diff --git a/.changelog/27991.txt b/.changelog/27991.txt new file mode 100644 index 00000000000..d863aa9b3fd --- /dev/null +++ b/.changelog/27991.txt @@ -0,0 +1,7 @@ +```release-note:bug +resource/aws_opsworks_permission: `stack_id` and `user_arn` are both Required and ForceNew +``` + +```release-note:enhancement +resource/aws_efs_mount_target: Add configurable timeouts for Create and Delete +``` \ No newline at end of file diff --git a/.ci/.semgrep.yml b/.ci/.semgrep.yml index 3f1094361e9..19864988d78 100644 --- a/.ci/.semgrep.yml +++ b/.ci/.semgrep.yml @@ -833,21 +833,6 @@ rules: pattern: int(*$VALUE) severity: WARNING - - id: aws-go-sdk-error-code-helper - languages: [go] - message: "Use `tfawserr` helpers for checking AWS Go SDK errors (e.g. `tfawserr.ErrMessageContains(err, CODE, MESSAGE)`)" - paths: - include: - - internal/ - patterns: - - pattern-either: - - pattern: if $AWSERR, $OK := $ORIGINALERR.(awserr.Error); $OK && $AWSERR.Code() == $CODE { $BODY } - - pattern: | - if $AWSERR, $OK := $ORIGINALERR.(awserr.Error); $OK { - if $AWSERR.Code() == $CODE { $BODY } - } - severity: WARNING - - id: fmt-Errorf-awserr-Error-Code languages: [go] message: Prefer `err` with `%w` format verb instead of `err.Code()` or `err.Message()` diff --git a/.ci/semgrep/aws/awserr.yml b/.ci/semgrep/aws/awserr.yml new file mode 100644 index 00000000000..dacb12f8164 --- /dev/null +++ b/.ci/semgrep/aws/awserr.yml @@ -0,0 +1,9 @@ +rules: + - id: aws-go-sdk-error-code-helper + languages: [go] + message: 'Use `tfawserr` helpers for checking AWS Go SDK v1 errors (e.g. `tfawserr.ErrMessageContains(err, CODE, MESSAGE)`)' + paths: + include: + - internal/ + pattern: $AWSERR, $OK := $ORIGINALERR.(awserr.Error) + severity: WARNING diff --git a/internal/acctest/crypto.go b/internal/acctest/crypto.go index 7419b75cd1d..00c4d8f6d91 100644 --- a/internal/acctest/crypto.go +++ b/internal/acctest/crypto.go @@ -25,7 +25,28 @@ const ( PEMBlockTypePublicKey = `PUBLIC KEY` ) -var tlsX509CertificateSerialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) //nolint:gomnd +var ( + tlsX509CertificateSerialNumberLimit = new(big.Int).Lsh(big.NewInt(1), 128) //nolint:gomnd +) + +// TLSPEMRemovePublicKeyEncapsulationBoundaries removes public key +// pre and post encapsulation boundaries from a PEM string. +func TLSPEMRemovePublicKeyEncapsulationBoundaries(pem string) string { + return removePEMEncapsulationBoundaries(pem, PEMBlockTypePublicKey) +} + +func removePEMEncapsulationBoundaries(pem, label string) string { + return strings.ReplaceAll(strings.ReplaceAll(pem, pemPreEncapsulationBoundary(label), ""), pemPostEncapsulationBoundary(label), "") +} + +// See https://www.rfc-editor.org/rfc/rfc7468#section-2. +func pemPreEncapsulationBoundary(label string) string { + return `-----BEGIN ` + label + `-----` +} + +func pemPostEncapsulationBoundary(label string) string { + return `-----END ` + label + `-----` +} // TLSECDSAPublicKeyPEM generates an ECDSA private key PEM string using the specified elliptic curve. // Wrap with TLSPEMEscapeNewlines() to allow simple fmt.Sprintf() @@ -392,6 +413,10 @@ func TLSPEMEscapeNewlines(pem string) string { return strings.ReplaceAll(pem, "\n", "\\n") } +func TLSPEMRemoveNewlines(pem string) string { + return strings.ReplaceAll(pem, "\n", "") +} + func ellipticCurveForName(name string) elliptic.Curve { switch name { case "P-224": diff --git a/internal/acctest/crypto_test.go b/internal/acctest/crypto_test.go index 73f66cf36bf..7e78f56d9ea 100644 --- a/internal/acctest/crypto_test.go +++ b/internal/acctest/crypto_test.go @@ -87,3 +87,47 @@ func TestTLSECDSAPublicKeyPEM(t *testing.T) { t.Errorf("key does not contain PUBLIC KEY: %s", publicKey) } } + +func TestTLSPEMEscapeNewlines(t *testing.T) { + t.Parallel() + + input := ` +ABCD +12345 +` + want := "\\nABCD\\n12345\\n" + + if got := acctest.TLSPEMEscapeNewlines(input); got != want { + t.Errorf("got: %s\nwant: %s", got, want) + } +} + +func TestTLSPEMRemovePublicKeyEncapsulationBoundaries(t *testing.T) { + t.Parallel() + + input := `-----BEGIN PUBLIC KEY----- +ABCD +12345 +-----END PUBLIC KEY----- +` + want := "\nABCD\n12345\n\n" + + if got := acctest.TLSPEMRemovePublicKeyEncapsulationBoundaries(input); got != want { + t.Errorf("got: %s\nwant: %s", got, want) + } +} + +func TestTLSPEMRemoveNewlines(t *testing.T) { + t.Parallel() + + input := ` +ABCD +12345 + +` + want := "ABCD12345" + + if got := acctest.TLSPEMRemoveNewlines(input); got != want { + t.Errorf("got: %s\nwant: %s", got, want) + } +} diff --git a/internal/service/apigateway/api_key.go b/internal/service/apigateway/api_key.go index 0579f9cd6c1..3431b89b5de 100644 --- a/internal/service/apigateway/api_key.go +++ b/internal/service/apigateway/api_key.go @@ -11,11 +11,13 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" 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" ) @@ -83,17 +85,20 @@ func resourceAPIKeyCreate(ctx context.Context, d *schema.ResourceData, meta inte conn := meta.(*conns.AWSClient).APIGatewayConn() defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - log.Printf("[DEBUG] Creating API Gateway API Key") - apiKey, err := conn.CreateApiKeyWithContext(ctx, &apigateway.CreateApiKeyInput{ - Name: aws.String(d.Get("name").(string)), + name := d.Get("name").(string) + input := &apigateway.CreateApiKeyInput{ Description: aws.String(d.Get("description").(string)), Enabled: aws.Bool(d.Get("enabled").(bool)), - Value: aws.String(d.Get("value").(string)), + Name: aws.String(name), Tags: Tags(tags.IgnoreAWS()), - }) + Value: aws.String(d.Get("value").(string)), + } + + apiKey, err := conn.CreateApiKeyWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating API Gateway API Key: %s", err) + return sdkdiag.AppendErrorf(diags, "creating API Gateway API Key (%s): %s", name, err) } d.SetId(aws.StringValue(apiKey.Id)) @@ -107,19 +112,15 @@ func resourceAPIKeyRead(ctx context.Context, d *schema.ResourceData, meta interf defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - log.Printf("[DEBUG] Reading API Gateway API Key: %s", d.Id()) + apiKey, err := FindAPIKeyByID(ctx, conn, d.Id()) - apiKey, err := conn.GetApiKeyWithContext(ctx, &apigateway.GetApiKeyInput{ - ApiKey: aws.String(d.Id()), - IncludeValue: aws.Bool(true), - }) - if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway API Key (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway API Key (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + if err != nil { return sdkdiag.AppendErrorf(diags, "reading API Gateway API Key (%s): %s", d.Id(), err) } @@ -187,31 +188,33 @@ func resourceAPIKeyUpdate(ctx context.Context, d *schema.ResourceData, meta inte var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Updating API Gateway API Key: %s", d.Id()) + if d.HasChangesExcept("tags", "tags_all") { + _, err := conn.UpdateApiKeyWithContext(ctx, &apigateway.UpdateApiKeyInput{ + ApiKey: aws.String(d.Id()), + PatchOperations: resourceAPIKeyUpdateOperations(d), + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating API Gateway API Key (%s): %s", d.Id(), err) + } + } if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") + if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { return sdkdiag.AppendErrorf(diags, "updating tags: %s", err) } } - _, err := conn.UpdateApiKeyWithContext(ctx, &apigateway.UpdateApiKeyInput{ - ApiKey: aws.String(d.Id()), - PatchOperations: resourceAPIKeyUpdateOperations(d), - }) - if err != nil { - return sdkdiag.AppendErrorf(diags, "updating API Gateway API Key (%s): %s", d.Id(), err) - } - return append(diags, resourceAPIKeyRead(ctx, d, meta)...) } func resourceAPIKeyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Deleting API Gateway API Key: %s", d.Id()) + log.Printf("[DEBUG] Deleting API Gateway API Key: %s", d.Id()) _, err := conn.DeleteApiKeyWithContext(ctx, &apigateway.DeleteApiKeyInput{ ApiKey: aws.String(d.Id()), }) @@ -226,3 +229,29 @@ func resourceAPIKeyDelete(ctx context.Context, d *schema.ResourceData, meta inte return diags } + +func FindAPIKeyByID(ctx context.Context, conn *apigateway.APIGateway, id string) (*apigateway.ApiKey, error) { + input := &apigateway.GetApiKeyInput{ + ApiKey: aws.String(id), + IncludeValue: aws.Bool(true), + } + + output, err := conn.GetApiKeyWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/apigateway/api_key_data_source.go b/internal/service/apigateway/api_key_data_source.go index e7b03fa4747..bfa5247fff0 100644 --- a/internal/service/apigateway/api_key_data_source.go +++ b/internal/service/apigateway/api_key_data_source.go @@ -5,7 +5,6 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/apigateway" "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" @@ -56,13 +55,11 @@ func dataSourceAPIKeyRead(ctx context.Context, d *schema.ResourceData, meta inte conn := meta.(*conns.AWSClient).APIGatewayConn() ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - apiKey, err := conn.GetApiKeyWithContext(ctx, &apigateway.GetApiKeyInput{ - ApiKey: aws.String(d.Get("id").(string)), - IncludeValue: aws.Bool(true), - }) + id := d.Get("id").(string) + apiKey, err := FindAPIKeyByID(ctx, conn, id) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading API Gateway API Key (%s): %s", d.Get("id").(string), err) + return sdkdiag.AppendErrorf(diags, "reading API Gateway API Key (%s): %s", id, err) } d.SetId(aws.StringValue(apiKey.Id)) @@ -76,5 +73,6 @@ func dataSourceAPIKeyRead(ctx context.Context, d *schema.ResourceData, meta inte if err := d.Set("tags", KeyValueTags(apiKey.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig).Map()); err != nil { return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) } + return diags } diff --git a/internal/service/apigateway/api_key_data_source_test.go b/internal/service/apigateway/api_key_data_source_test.go index b14e37271dc..fdd87c43cb9 100644 --- a/internal/service/apigateway/api_key_data_source_test.go +++ b/internal/service/apigateway/api_key_data_source_test.go @@ -11,9 +11,9 @@ import ( ) func TestAccAPIGatewayAPIKeyDataSource_basic(t *testing.T) { - rName := sdkacctest.RandString(8) - resourceName1 := "aws_api_gateway_api_key.example_key" - dataSourceName1 := "data.aws_api_gateway_api_key.test_key" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_api_gateway_api_key.test" + dataSourceName := "data.aws_api_gateway_api_key.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -23,14 +23,14 @@ func TestAccAPIGatewayAPIKeyDataSource_basic(t *testing.T) { { Config: testAccAPIKeyDataSourceConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttrPair(resourceName1, "id", dataSourceName1, "id"), - resource.TestCheckResourceAttrPair(resourceName1, "name", dataSourceName1, "name"), - resource.TestCheckResourceAttrPair(resourceName1, "value", dataSourceName1, "value"), - resource.TestCheckResourceAttrPair(resourceName1, "enabled", dataSourceName1, "enabled"), - resource.TestCheckResourceAttrPair(resourceName1, "description", dataSourceName1, "description"), - resource.TestCheckResourceAttrSet(dataSourceName1, "last_updated_date"), - resource.TestCheckResourceAttrSet(dataSourceName1, "created_date"), - resource.TestCheckResourceAttr(dataSourceName1, "tags.%", "0"), + resource.TestCheckResourceAttrPair(resourceName, "id", dataSourceName, "id"), + resource.TestCheckResourceAttrPair(resourceName, "name", dataSourceName, "name"), + resource.TestCheckResourceAttrPair(resourceName, "value", dataSourceName, "value"), + resource.TestCheckResourceAttrPair(resourceName, "enabled", dataSourceName, "enabled"), + resource.TestCheckResourceAttrPair(resourceName, "description", dataSourceName, "description"), + resource.TestCheckResourceAttrSet(dataSourceName, "last_updated_date"), + resource.TestCheckResourceAttrSet(dataSourceName, "created_date"), + resource.TestCheckResourceAttr(dataSourceName, "tags.%", "0"), ), }, }, @@ -39,12 +39,12 @@ func TestAccAPIGatewayAPIKeyDataSource_basic(t *testing.T) { func testAccAPIKeyDataSourceConfig_basic(r string) string { return fmt.Sprintf(` -resource "aws_api_gateway_api_key" "example_key" { - name = "%s" +resource "aws_api_gateway_api_key" "test" { + name = %[1]q } -data "aws_api_gateway_api_key" "test_key" { - id = aws_api_gateway_api_key.example_key.id +data "aws_api_gateway_api_key" "test" { + id = aws_api_gateway_api_key.test.id } `, r) } diff --git a/internal/service/apigateway/api_key_test.go b/internal/service/apigateway/api_key_test.go index 473ba36d878..3b3d406f450 100644 --- a/internal/service/apigateway/api_key_test.go +++ b/internal/service/apigateway/api_key_test.go @@ -7,7 +7,6 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -15,6 +14,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayAPIKey_basic(t *testing.T) { @@ -221,7 +221,7 @@ func TestAccAPIGatewayAPIKey_disappears(t *testing.T) { }) } -func testAccCheckAPIKeyExists(ctx context.Context, n string, res *apigateway.ApiKey) resource.TestCheckFunc { +func testAccCheckAPIKeyExists(ctx context.Context, n string, v *apigateway.ApiKey) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -229,24 +229,18 @@ func testAccCheckAPIKeyExists(ctx context.Context, n string, res *apigateway.Api } if rs.Primary.ID == "" { - return fmt.Errorf("No API Gateway ApiKey ID is set") + return fmt.Errorf("No API Gateway API Key ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetApiKeyInput{ - ApiKey: aws.String(rs.Primary.ID), - } - describe, err := conn.GetApiKeyWithContext(ctx, req) + output, err := tfapigateway.FindAPIKeyByID(ctx, conn, rs.Primary.ID) + if err != nil { return err } - if *describe.Id != rs.Primary.ID { - return fmt.Errorf("APIGateway ApiKey not found") - } - - *res = *describe + *v = *output return nil } @@ -261,24 +255,17 @@ func testAccCheckAPIKeyDestroy(ctx context.Context) resource.TestCheckFunc { continue } - describe, err := conn.GetApiKeysWithContext(ctx, &apigateway.GetApiKeysInput{}) + _, err := tfapigateway.FindAPIKeyByID(ctx, conn, rs.Primary.ID) - if err == nil { - if len(describe.Items) != 0 && - *describe.Items[0].Id == rs.Primary.ID { - return fmt.Errorf("API Gateway ApiKey still exists") - } + if tfresource.NotFound(err) { + continue } - aws2err, ok := err.(awserr.Error) - if !ok { - return err - } - if aws2err.Code() != "NotFoundException" { + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway API Key %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/apigateway/authorizer.go b/internal/service/apigateway/authorizer.go index 7695ced063e..b39a3f7fc31 100644 --- a/internal/service/apigateway/authorizer.go +++ b/internal/service/apigateway/authorizer.go @@ -11,11 +11,13 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -102,22 +104,25 @@ func ResourceAuthorizer() *schema.Resource { func resourceAuthorizerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - var postCreateOps []*apigateway.PatchOperation - input := apigateway.CreateAuthorizerInput{ + if err := validateAuthorizerType(d); err != nil { + return sdkdiag.AppendErrorf(diags, "creating API Gateway Authorizer: %s", err) + } + + var postCreateOps []*apigateway.PatchOperation + name := d.Get("name").(string) + input := &apigateway.CreateAuthorizerInput{ IdentitySource: aws.String(d.Get("identity_source").(string)), - Name: aws.String(d.Get("name").(string)), + Name: aws.String(name), RestApiId: aws.String(d.Get("rest_api_id").(string)), Type: aws.String(d.Get("type").(string)), AuthorizerResultTtlInSeconds: aws.Int64(int64(d.Get("authorizer_result_ttl_in_seconds").(int))), } - if err := validateAuthorizerType(d); err != nil { - return sdkdiag.AppendErrorf(diags, "creating API Gateway Authorizer: %s", err) - } if v, ok := d.GetOk("authorizer_uri"); ok { input.AuthorizerUri = aws.String(v.(string)) } + if v, ok := d.GetOk("authorizer_credentials"); ok { // While the CreateAuthorizer method allows one to pass AuthorizerCredentials // regardless of authorizer Type, the API ignores this setting if the authorizer @@ -137,29 +142,30 @@ func resourceAuthorizerCreate(ctx context.Context, d *schema.ResourceData, meta if v, ok := d.GetOk("identity_validation_expression"); ok { input.IdentityValidationExpression = aws.String(v.(string)) } + if v, ok := d.GetOk("provider_arns"); ok { input.ProviderARNs = flex.ExpandStringSet(v.(*schema.Set)) } - log.Printf("[INFO] Creating API Gateway Authorizer: %s", input) - out, err := conn.CreateAuthorizerWithContext(ctx, &input) + output, err := conn.CreateAuthorizerWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating API Gateway Authorizer: %s", err) + return sdkdiag.AppendErrorf(diags, "creating API Gateway Authorizer (%s): %s", name, err) } - d.SetId(aws.StringValue(out.Id)) + d.SetId(aws.StringValue(output.Id)) if postCreateOps != nil { - input := apigateway.UpdateAuthorizerInput{ + input := &apigateway.UpdateAuthorizerInput{ AuthorizerId: aws.String(d.Id()), PatchOperations: postCreateOps, RestApiId: input.RestApiId, } - log.Printf("[INFO] Applying update operations to API Gateway Authorizer: %s", d.Id()) - _, err := conn.UpdateAuthorizerWithContext(ctx, &input) + _, err := conn.UpdateAuthorizerWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "applying update operations to API Gateway Authorizer (%s) failed: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating API Gateway Authorizer (%s): %s", d.Id(), err) } } @@ -170,33 +176,32 @@ func resourceAuthorizerRead(ctx context.Context, d *schema.ResourceData, meta in var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[INFO] Reading API Gateway Authorizer %s", d.Id()) + apiID := d.Get("rest_api_id").(string) + authorizer, err := FindAuthorizerByTwoPartKey(ctx, conn, d.Id(), apiID) - restApiId := d.Get("rest_api_id").(string) - input := apigateway.GetAuthorizerInput{ - AuthorizerId: aws.String(d.Id()), - RestApiId: aws.String(restApiId), + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway Authorizer (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - authorizer, err := conn.GetAuthorizerWithContext(ctx, &input) if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway Authorizer (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } return sdkdiag.AppendErrorf(diags, "reading API Gateway Authorizer (%s): %s", d.Id(), err) } - log.Printf("[DEBUG] Received API Gateway Authorizer: %s", authorizer) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "apigateway", + Region: meta.(*conns.AWSClient).Region, + Resource: fmt.Sprintf("/restapis/%s/authorizers/%s", apiID, d.Id()), + }.String() + d.Set("arn", arn) d.Set("authorizer_credentials", authorizer.AuthorizerCredentials) - if authorizer.AuthorizerResultTtlInSeconds != nil { // nosemgrep:ci.helper-schema-ResourceData-Set-extraneous-nil-check d.Set("authorizer_result_ttl_in_seconds", authorizer.AuthorizerResultTtlInSeconds) } else { d.Set("authorizer_result_ttl_in_seconds", DefaultAuthorizerTTL) } - d.Set("authorizer_uri", authorizer.AuthorizerUri) d.Set("identity_source", authorizer.IdentitySource) d.Set("identity_validation_expression", authorizer.IdentityValidationExpression) @@ -204,14 +209,6 @@ func resourceAuthorizerRead(ctx context.Context, d *schema.ResourceData, meta in d.Set("type", authorizer.Type) d.Set("provider_arns", flex.FlattenStringSet(authorizer.ProviderARNs)) - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Service: "apigateway", - Region: meta.(*conns.AWSClient).Region, - Resource: fmt.Sprintf("/restapis/%s/authorizers/%s", restApiId, d.Id()), - }.String() - d.Set("arn", arn) - return diags } @@ -219,11 +216,6 @@ func resourceAuthorizerUpdate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - input := apigateway.UpdateAuthorizerInput{ - AuthorizerId: aws.String(d.Id()), - RestApiId: aws.String(d.Get("rest_api_id").(string)), - } - operations := make([]*apigateway.PatchOperation, 0) if d.HasChange("authorizer_uri") { @@ -298,12 +290,16 @@ func resourceAuthorizerUpdate(ctx context.Context, d *schema.ResourceData, meta } } - input.PatchOperations = operations + input := &apigateway.UpdateAuthorizerInput{ + AuthorizerId: aws.String(d.Id()), + PatchOperations: operations, + RestApiId: aws.String(d.Get("rest_api_id").(string)), + } + + _, err := conn.UpdateAuthorizerWithContext(ctx, input) - log.Printf("[INFO] Updating API Gateway Authorizer: %s", input) - _, err := conn.UpdateAuthorizerWithContext(ctx, &input) if err != nil { - return sdkdiag.AppendErrorf(diags, "updating API Gateway Authorizer failed: %s", err) + return sdkdiag.AppendErrorf(diags, "updating API Gateway Authorizer (%s): %s", d.Id(), err) } return append(diags, resourceAuthorizerRead(ctx, d, meta)...) @@ -312,12 +308,13 @@ func resourceAuthorizerUpdate(ctx context.Context, d *schema.ResourceData, meta func resourceAuthorizerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - input := apigateway.DeleteAuthorizerInput{ + + log.Printf("[INFO] Deleting API Gateway Authorizer: %s", d.Id()) + _, err := conn.DeleteAuthorizerWithContext(ctx, &apigateway.DeleteAuthorizerInput{ AuthorizerId: aws.String(d.Id()), RestApiId: aws.String(d.Get("rest_api_id").(string)), - } - log.Printf("[INFO] Deleting API Gateway Authorizer: %s", input) - _, err := conn.DeleteAuthorizerWithContext(ctx, &input) + }) + if err != nil { // XXX: Figure out a way to delete the method that depends on the authorizer first // otherwise the authorizer will be dangling until the API is deleted @@ -360,3 +357,29 @@ func validateAuthorizerType(d *schema.ResourceData) error { return nil } + +func FindAuthorizerByTwoPartKey(ctx context.Context, conn *apigateway.APIGateway, authorizerID, apiID string) (*apigateway.Authorizer, error) { + input := &apigateway.GetAuthorizerInput{ + AuthorizerId: aws.String(authorizerID), + RestApiId: aws.String(apiID), + } + + output, err := conn.GetAuthorizerWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/apigateway/authorizer_test.go b/internal/service/apigateway/authorizer_test.go index 02080547565..3430a06f2dc 100644 --- a/internal/service/apigateway/authorizer_test.go +++ b/internal/service/apigateway/authorizer_test.go @@ -7,8 +7,6 @@ import ( "strconv" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -16,6 +14,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayAuthorizer_basic(t *testing.T) { @@ -316,7 +315,7 @@ func TestAccAPIGatewayAuthorizer_disappears(t *testing.T) { }) } -func testAccCheckAuthorizerExists(ctx context.Context, n string, res *apigateway.Authorizer) resource.TestCheckFunc { +func testAccCheckAuthorizerExists(ctx context.Context, n string, v *apigateway.Authorizer) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -329,16 +328,13 @@ func testAccCheckAuthorizerExists(ctx context.Context, n string, res *apigateway conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetAuthorizerInput{ - AuthorizerId: aws.String(rs.Primary.ID), - RestApiId: aws.String(rs.Primary.Attributes["rest_api_id"]), - } - describe, err := conn.GetAuthorizerWithContext(ctx, req) + output, err := tfapigateway.FindAuthorizerByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["rest_api_id"]) + if err != nil { return err } - *res = *describe + *v = *output return nil } @@ -353,25 +349,17 @@ func testAccCheckAuthorizerDestroy(ctx context.Context) resource.TestCheckFunc { continue } - req := &apigateway.GetAuthorizerInput{ - AuthorizerId: aws.String(rs.Primary.ID), - RestApiId: aws.String(rs.Primary.Attributes["rest_api_id"]), - } - _, err := conn.GetAuthorizerWithContext(ctx, req) + _, err := tfapigateway.FindAuthorizerByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["rest_api_id"]) - if err == nil { - return fmt.Errorf("API Gateway Authorizer still exists") + if tfresource.NotFound(err) { + continue } - aws2err, ok := err.(awserr.Error) - if !ok { - return err - } - if aws2err.Code() != apigateway.ErrCodeNotFoundException { + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway Authorizer %s still exists", rs.Primary.ID) } return nil @@ -389,7 +377,7 @@ func testAccAuthorizerImportStateIdFunc(resourceName string) resource.ImportStat } } -func testAccAuthorizerBaseConfig(rName string) string { +func testAccAuthorizerConfig_base(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { name = %[1]q @@ -466,18 +454,18 @@ resource "aws_lambda_function" "test" { } func testAccAuthorizerConfig_lambda(rName string) string { - return testAccAuthorizerBaseConfig(rName) + fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_api_gateway_authorizer" "test" { name = %[1]q rest_api_id = aws_api_gateway_rest_api.test.id authorizer_uri = aws_lambda_function.test.invoke_arn authorizer_credentials = aws_iam_role.test.arn } -`, rName) +`, rName)) } func testAccAuthorizerConfig_lambdaUpdate(rName string) string { - return testAccAuthorizerBaseConfig(rName) + fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_api_gateway_authorizer" "test" { name = "%[1]s_modified" rest_api_id = aws_api_gateway_rest_api.test.id @@ -486,11 +474,11 @@ resource "aws_api_gateway_authorizer" "test" { authorizer_result_ttl_in_seconds = 360 identity_validation_expression = ".*" } -`, rName) +`, rName)) } func testAccAuthorizerConfig_lambdaNoCache(rName string) string { - return testAccAuthorizerBaseConfig(rName) + fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_api_gateway_authorizer" "test" { name = "%[1]s_modified" rest_api_id = aws_api_gateway_rest_api.test.id @@ -499,7 +487,7 @@ resource "aws_api_gateway_authorizer" "test" { authorizer_result_ttl_in_seconds = 0 identity_validation_expression = ".*" } -`, rName) +`, rName)) } func testAccAuthorizerConfig_cognito(rName string) string { @@ -586,24 +574,24 @@ resource "aws_api_gateway_authorizer" "test" { } func testAccAuthorizerConfig_authTypeValidationDefaultToken(rName string) string { - return testAccAuthorizerBaseConfig(rName) + fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_api_gateway_authorizer" "test" { - name = "%s" + name = %[1]q rest_api_id = aws_api_gateway_rest_api.test.id authorizer_credentials = aws_iam_role.test.arn } -`, rName) +`, rName)) } func testAccAuthorizerConfig_authTypeValidationRequest(rName string) string { - return testAccAuthorizerBaseConfig(rName) + fmt.Sprintf(` + return acctest.ConfigCompose(testAccAuthorizerConfig_base(rName), fmt.Sprintf(` resource "aws_api_gateway_authorizer" "test" { - name = "%s" + name = %[1]q type = "REQUEST" rest_api_id = aws_api_gateway_rest_api.test.id authorizer_credentials = aws_iam_role.test.arn } -`, rName) +`, rName)) } func testAccAuthorizerConfig_authTypeValidationCognito(rName string) string { diff --git a/internal/service/apigateway/client_certificate.go b/internal/service/apigateway/client_certificate.go index d42aa1c8093..654cf6a694d 100644 --- a/internal/service/apigateway/client_certificate.go +++ b/internal/service/apigateway/client_certificate.go @@ -10,10 +10,12 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" 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" ) @@ -23,28 +25,29 @@ func ResourceClientCertificate() *schema.Resource { ReadWithoutTimeout: resourceClientCertificateRead, UpdateWithoutTimeout: resourceClientCertificateUpdate, DeleteWithoutTimeout: resourceClientCertificateDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ - "description": { + "arn": { Type: schema.TypeString, - Optional: true, + Computed: true, }, "created_date": { Type: schema.TypeString, Computed: true, }, - "expiration_date": { + "description": { Type: schema.TypeString, - Computed: true, + Optional: true, }, - "pem_encoded_certificate": { + "expiration_date": { Type: schema.TypeString, Computed: true, }, - "arn": { + "pem_encoded_certificate": { Type: schema.TypeString, Computed: true, }, @@ -62,20 +65,23 @@ func resourceClientCertificateCreate(ctx context.Context, d *schema.ResourceData defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - input := apigateway.GenerateClientCertificateInput{} + input := &apigateway.GenerateClientCertificateInput{} + if v, ok := d.GetOk("description"); ok { input.Description = aws.String(v.(string)) } + if len(tags) > 0 { input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Generating API Gateway Client Certificate: %s", input) - out, err := conn.GenerateClientCertificateWithContext(ctx, &input) + + output, err := conn.GenerateClientCertificateWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Failed to generate client certificate: %s", err) + return sdkdiag.AppendErrorf(diags, "creating API Gateway Client Certificate: %s", err) } - d.SetId(aws.StringValue(out.ClientCertificateId)) + d.SetId(aws.StringValue(output.ClientCertificateId)) return append(diags, resourceClientCertificateRead(ctx, d, meta)...) } @@ -86,20 +92,31 @@ func resourceClientCertificateRead(ctx context.Context, d *schema.ResourceData, defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - input := apigateway.GetClientCertificateInput{ - ClientCertificateId: aws.String(d.Id()), + cert, err := FindClientCertificateByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway Client Certificate (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - out, err := conn.GetClientCertificateWithContext(ctx, &input) + if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway Client Certificate (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } return sdkdiag.AppendErrorf(diags, "reading API Gateway Client Certificate (%s): %s", d.Id(), err) } - tags := KeyValueTags(out.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "apigateway", + Region: meta.(*conns.AWSClient).Region, + Resource: fmt.Sprintf("/clientcertificates/%s", d.Id()), + }.String() + d.Set("arn", arn) + d.Set("created_date", cert.CreatedDate.String()) + d.Set("description", cert.Description) + d.Set("expiration_date", cert.ExpirationDate.String()) + d.Set("pem_encoded_certificate", cert.PemEncodedCertificate) + + tags := KeyValueTags(cert.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -110,19 +127,6 @@ func resourceClientCertificateRead(ctx context.Context, d *schema.ResourceData, return sdkdiag.AppendErrorf(diags, "setting tags_all: %s", err) } - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Service: "apigateway", - Region: meta.(*conns.AWSClient).Region, - Resource: fmt.Sprintf("/clientcertificates/%s", d.Id()), - }.String() - d.Set("arn", arn) - - d.Set("description", out.Description) - d.Set("created_date", out.CreatedDate.String()) - d.Set("expiration_date", out.ExpirationDate.String()) - d.Set("pem_encoded_certificate", out.PemEncodedCertificate) - return diags } @@ -130,28 +134,28 @@ func resourceClientCertificateUpdate(ctx context.Context, d *schema.ResourceData var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - operations := make([]*apigateway.PatchOperation, 0) - if d.HasChange("description") { - operations = append(operations, &apigateway.PatchOperation{ - Op: aws.String(apigateway.OpReplace), - Path: aws.String("/description"), - Value: aws.String(d.Get("description").(string)), - }) - } + if d.HasChangesExcept("tags", "tags_all") { + input := &apigateway.UpdateClientCertificateInput{ + ClientCertificateId: aws.String(d.Id()), + PatchOperations: []*apigateway.PatchOperation{ + { + Op: aws.String(apigateway.OpReplace), + Path: aws.String("/description"), + Value: aws.String(d.Get("description").(string)), + }, + }, + } - input := apigateway.UpdateClientCertificateInput{ - ClientCertificateId: aws.String(d.Id()), - PatchOperations: operations, - } + _, err := conn.UpdateClientCertificateWithContext(ctx, input) - log.Printf("[DEBUG] Updating API Gateway Client Certificate: %s", input) - _, err := conn.UpdateClientCertificateWithContext(ctx, &input) - if err != nil { - return sdkdiag.AppendErrorf(diags, "Updating API Gateway Client Certificate failed: %s", err) + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating API Gateway Client Certificate (%s): %s", d.Id(), err) + } } if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") + if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { return sdkdiag.AppendErrorf(diags, "updating tags: %s", err) } @@ -163,14 +167,40 @@ func resourceClientCertificateUpdate(ctx context.Context, d *schema.ResourceData func resourceClientCertificateDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() + log.Printf("[DEBUG] Deleting API Gateway Client Certificate: %s", d.Id()) - input := apigateway.DeleteClientCertificateInput{ + _, err := conn.DeleteClientCertificateWithContext(ctx, &apigateway.DeleteClientCertificateInput{ ClientCertificateId: aws.String(d.Id()), - } - _, err := conn.DeleteClientCertificateWithContext(ctx, &input) + }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Deleting API Gateway Client Certificate failed: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting API Gateway Client Certificate (%s): %s", d.Id(), err) } return diags } + +func FindClientCertificateByID(ctx context.Context, conn *apigateway.APIGateway, id string) (*apigateway.ClientCertificate, error) { + input := &apigateway.GetClientCertificateInput{ + ClientCertificateId: aws.String(id), + } + + output, err := conn.GetClientCertificateWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/apigateway/client_certificate_test.go b/internal/service/apigateway/client_certificate_test.go index 6b4469e2596..c2f502d6f75 100644 --- a/internal/service/apigateway/client_certificate_test.go +++ b/internal/service/apigateway/client_certificate_test.go @@ -6,14 +6,13 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" "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" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayClientCertificate_basic(t *testing.T) { @@ -120,7 +119,7 @@ func TestAccAPIGatewayClientCertificate_disappears(t *testing.T) { }) } -func testAccCheckClientCertificateExists(ctx context.Context, n string, res *apigateway.ClientCertificate) resource.TestCheckFunc { +func testAccCheckClientCertificateExists(ctx context.Context, n string, v *apigateway.ClientCertificate) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -133,15 +132,13 @@ func testAccCheckClientCertificateExists(ctx context.Context, n string, res *api conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetClientCertificateInput{ - ClientCertificateId: aws.String(rs.Primary.ID), - } - out, err := conn.GetClientCertificateWithContext(ctx, req) + output, err := tfapigateway.FindClientCertificateByID(ctx, conn, rs.Primary.ID) + if err != nil { return err } - *res = *out + *v = *output return nil } @@ -156,23 +153,17 @@ func testAccCheckClientCertificateDestroy(ctx context.Context) resource.TestChec continue } - req := &apigateway.GetClientCertificateInput{ - ClientCertificateId: aws.String(rs.Primary.ID), - } - out, err := conn.GetClientCertificateWithContext(ctx, req) - if err == nil { - return fmt.Errorf("API Gateway Client Certificate still exists: %s", out) - } + _, err := tfapigateway.FindClientCertificateByID(ctx, conn, rs.Primary.ID) - awsErr, ok := err.(awserr.Error) - if !ok { - return err + if tfresource.NotFound(err) { + continue } - if awsErr.Code() != apigateway.ErrCodeNotFoundException { + + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway Client Certificate %s still exists", rs.Primary.ID) } return nil @@ -197,7 +188,7 @@ resource "aws_api_gateway_client_certificate" "test" { description = "Hello from TF acceptance test" tags = { - %q = %q + %[1]q = %[2]q } } `, tagKey1, tagValue1) @@ -209,8 +200,8 @@ resource "aws_api_gateway_client_certificate" "test" { description = "Hello from TF acceptance test" tags = { - %q = %q - %q = %q + %[1]q = %[2]q + %[3]q = %[4]q } } `, tagKey1, tagValue1, tagKey2, tagValue2) diff --git a/internal/service/apigateway/gateway_response.go b/internal/service/apigateway/gateway_response.go index b5fc1aa0091..8f7fbca8ed5 100644 --- a/internal/service/apigateway/gateway_response.go +++ b/internal/service/apigateway/gateway_response.go @@ -10,9 +10,12 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceGatewayResponse() *schema.Resource { @@ -21,6 +24,7 @@ func ResourceGatewayResponse() *schema.Resource { ReadWithoutTimeout: resourceGatewayResponseRead, UpdateWithoutTimeout: resourceGatewayResponsePut, DeleteWithoutTimeout: resourceGatewayResponseDelete, + Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") @@ -37,34 +41,30 @@ func ResourceGatewayResponse() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "rest_api_id": { + "response_parameters": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "response_templates": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "response_type": { Type: schema.TypeString, Required: true, ForceNew: true, }, - - "response_type": { + "rest_api_id": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "status_code": { Type: schema.TypeString, Optional: true, }, - - "response_templates": { - Type: schema.TypeMap, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - }, - - "response_parameters": { - Type: schema.TypeMap, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - }, }, } } @@ -73,40 +73,32 @@ func resourceGatewayResponsePut(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - templates := make(map[string]string) - if kv, ok := d.GetOk("response_templates"); ok { - for k, v := range kv.(map[string]interface{}) { - templates[k] = v.(string) - } + input := &apigateway.PutGatewayResponseInput{ + ResponseType: aws.String(d.Get("response_type").(string)), + RestApiId: aws.String(d.Get("rest_api_id").(string)), } - parameters := make(map[string]string) - if kv, ok := d.GetOk("response_parameters"); ok { - for k, v := range kv.(map[string]interface{}) { - parameters[k] = v.(string) - } + if v, ok := d.GetOk("response_parameters"); ok && len(v.(map[string]interface{})) > 0 { + input.ResponseParameters = flex.ExpandStringMap(v.(map[string]interface{})) } - input := apigateway.PutGatewayResponseInput{ - RestApiId: aws.String(d.Get("rest_api_id").(string)), - ResponseType: aws.String(d.Get("response_type").(string)), - ResponseTemplates: aws.StringMap(templates), - ResponseParameters: aws.StringMap(parameters), + if v, ok := d.GetOk("response_templates"); ok && len(v.(map[string]interface{})) > 0 { + input.ResponseTemplates = flex.ExpandStringMap(v.(map[string]interface{})) } if v, ok := d.GetOk("status_code"); ok { input.StatusCode = aws.String(v.(string)) } - log.Printf("[DEBUG] Putting API Gateway Gateway Response: %s", input) + _, err := conn.PutGatewayResponseWithContext(ctx, input) - _, err := conn.PutGatewayResponseWithContext(ctx, &input) if err != nil { - return sdkdiag.AppendErrorf(diags, "Error putting API Gateway Gateway Response: %s", err) + return sdkdiag.AppendErrorf(diags, "putting API Gateway Gateway Response: %s", err) } - d.SetId(fmt.Sprintf("aggr-%s-%s", d.Get("rest_api_id").(string), d.Get("response_type").(string))) - log.Printf("[DEBUG] API Gateway Gateway Response put (%q)", d.Id()) + if d.IsNewResource() { + d.SetId(fmt.Sprintf("aggr-%s-%s", d.Get("rest_api_id").(string), d.Get("response_type").(string))) + } return append(diags, resourceGatewayResponseRead(ctx, d, meta)...) } @@ -115,26 +107,22 @@ func resourceGatewayResponseRead(ctx context.Context, d *schema.ResourceData, me var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Reading API Gateway Gateway Response %s", d.Id()) - gatewayResponse, err := conn.GetGatewayResponseWithContext(ctx, &apigateway.GetGatewayResponseInput{ - RestApiId: aws.String(d.Get("rest_api_id").(string)), - ResponseType: aws.String(d.Get("response_type").(string)), - }) - if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway Gateway Response (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "reading API Gateway Response (%s): %s", d.Id(), err) + gatewayResponse, err := FindGatewayResponseByTwoPartKey(ctx, conn, d.Get("response_type").(string), d.Get("rest_api_id").(string)) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway Gateway Response (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - log.Printf("[DEBUG] Received API Gateway Gateway Response: %s", gatewayResponse) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading API Gateway Gateway Response (%s): %s", d.Id(), err) + } + d.Set("response_parameters", aws.StringValueMap(gatewayResponse.ResponseParameters)) + d.Set("response_templates", aws.StringValueMap(gatewayResponse.ResponseTemplates)) d.Set("response_type", gatewayResponse.ResponseType) d.Set("status_code", gatewayResponse.StatusCode) - d.Set("response_templates", aws.StringValueMap(gatewayResponse.ResponseTemplates)) - d.Set("response_parameters", aws.StringValueMap(gatewayResponse.ResponseParameters)) return diags } @@ -142,11 +130,11 @@ func resourceGatewayResponseRead(ctx context.Context, d *schema.ResourceData, me func resourceGatewayResponseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Deleting API Gateway Gateway Response: %s", d.Id()) + log.Printf("[DEBUG] Deleting API Gateway Gateway Response: %s", d.Id()) _, err := conn.DeleteGatewayResponseWithContext(ctx, &apigateway.DeleteGatewayResponseInput{ - RestApiId: aws.String(d.Get("rest_api_id").(string)), ResponseType: aws.String(d.Get("response_type").(string)), + RestApiId: aws.String(d.Get("rest_api_id").(string)), }) if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { @@ -154,7 +142,34 @@ func resourceGatewayResponseDelete(ctx context.Context, d *schema.ResourceData, } if err != nil { - return sdkdiag.AppendErrorf(diags, "Error deleting API Gateway gateway response: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting API Gateway Gateway Response (%s): %s", d.Id(), err) } + return diags } + +func FindGatewayResponseByTwoPartKey(ctx context.Context, conn *apigateway.APIGateway, responseType, apiID string) (*apigateway.UpdateGatewayResponseOutput, error) { + input := &apigateway.GetGatewayResponseInput{ + ResponseType: aws.String(responseType), + RestApiId: aws.String(apiID), + } + + output, err := conn.GetGatewayResponseWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/apigateway/gateway_response_test.go b/internal/service/apigateway/gateway_response_test.go index 89193626bd6..d01f8de8fff 100644 --- a/internal/service/apigateway/gateway_response_test.go +++ b/internal/service/apigateway/gateway_response_test.go @@ -5,8 +5,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,13 +12,13 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayGatewayResponse_basic(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.UpdateGatewayResponseOutput - - rName := sdkacctest.RandString(10) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_gateway_response.test" resource.ParallelTest(t, resource.TestCase{ @@ -39,7 +37,12 @@ func TestAccAPIGatewayGatewayResponse_basic(t *testing.T) { resource.TestCheckNoResourceAttr(resourceName, "response_templates.application/json"), ), }, - + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccGatewayResponseImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, { Config: testAccGatewayResponseConfig_update(rName), Check: resource.ComposeTestCheckFunc( @@ -50,12 +53,6 @@ func TestAccAPIGatewayGatewayResponse_basic(t *testing.T) { resource.TestCheckNoResourceAttr(resourceName, "response_parameters.gatewayresponse.header.Authorization"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccGatewayResponseImportStateIdFunc(resourceName), - ImportStateVerify: true, - }, }, }) } @@ -63,8 +60,7 @@ func TestAccAPIGatewayGatewayResponse_basic(t *testing.T) { func TestAccAPIGatewayGatewayResponse_disappears(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.UpdateGatewayResponseOutput - - rName := sdkacctest.RandString(10) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_gateway_response.test" resource.ParallelTest(t, resource.TestCase{ @@ -85,7 +81,7 @@ func TestAccAPIGatewayGatewayResponse_disappears(t *testing.T) { }) } -func testAccCheckGatewayResponseExists(ctx context.Context, n string, res *apigateway.UpdateGatewayResponseOutput) resource.TestCheckFunc { +func testAccCheckGatewayResponseExists(ctx context.Context, n string, v *apigateway.UpdateGatewayResponseOutput) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -98,16 +94,13 @@ func testAccCheckGatewayResponseExists(ctx context.Context, n string, res *apiga conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetGatewayResponseInput{ - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - ResponseType: aws.String(rs.Primary.Attributes["response_type"]), - } - describe, err := conn.GetGatewayResponseWithContext(ctx, req) + output, err := tfapigateway.FindGatewayResponseByTwoPartKey(ctx, conn, rs.Primary.Attributes["response_type"], rs.Primary.Attributes["rest_api_id"]) + if err != nil { return err } - *res = *describe + *v = *output return nil } @@ -122,25 +115,17 @@ func testAccCheckGatewayResponseDestroy(ctx context.Context) resource.TestCheckF continue } - req := &apigateway.GetGatewayResponseInput{ - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - ResponseType: aws.String(rs.Primary.Attributes["response_type"]), - } - _, err := conn.GetGatewayResponseWithContext(ctx, req) + _, err := tfapigateway.FindGatewayResponseByTwoPartKey(ctx, conn, rs.Primary.Attributes["response_type"], rs.Primary.Attributes["rest_api_id"]) - if err == nil { - return fmt.Errorf("API Gateway Gateway Response still exists") + if tfresource.NotFound(err) { + continue } - aws2err, ok := err.(awserr.Error) - if !ok { - return err - } - if aws2err.Code() != "NotFoundException" { + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway Gateway Response %s still exists", rs.Primary.ID) } return nil @@ -161,7 +146,7 @@ func testAccGatewayResponseImportStateIdFunc(resourceName string) resource.Impor func testAccGatewayResponseConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { - name = "%s" + name = %[1]q } resource "aws_api_gateway_gateway_response" "test" { @@ -183,7 +168,7 @@ resource "aws_api_gateway_gateway_response" "test" { func testAccGatewayResponseConfig_update(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { - name = "%s" + name = %[1]q } resource "aws_api_gateway_gateway_response" "test" { diff --git a/internal/service/apigateway/integration.go b/internal/service/apigateway/integration.go index 7d20febd758..d20cfa0e717 100644 --- a/internal/service/apigateway/integration.go +++ b/internal/service/apigateway/integration.go @@ -11,11 +11,13 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceIntegration() *schema.Resource { @@ -24,6 +26,7 @@ func ResourceIntegration() *schema.Resource { ReadWithoutTimeout: resourceIntegrationRead, UpdateWithoutTimeout: resourceIntegrationUpdate, DeleteWithoutTimeout: resourceIntegrationDelete, + Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") @@ -42,89 +45,48 @@ func ResourceIntegration() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "rest_api_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "resource_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "http_method": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validHTTPMethod(), - }, - - "type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.StringInSlice([]string{ - apigateway.IntegrationTypeHttp, - apigateway.IntegrationTypeAws, - apigateway.IntegrationTypeMock, - apigateway.IntegrationTypeHttpProxy, - apigateway.IntegrationTypeAwsProxy, - }, false), + "cache_key_parameters": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, }, - - "connection_type": { + "cache_namespace": { Type: schema.TypeString, Optional: true, - Default: apigateway.ConnectionTypeInternet, - ValidateFunc: validation.StringInSlice([]string{ - apigateway.ConnectionTypeInternet, - apigateway.ConnectionTypeVpcLink, - }, false), + Computed: true, }, - "connection_id": { Type: schema.TypeString, Optional: true, }, - - "uri": { - Type: schema.TypeString, - Optional: true, + "connection_type": { + Type: schema.TypeString, + Optional: true, + Default: apigateway.ConnectionTypeInternet, + ValidateFunc: validation.StringInSlice(apigateway.ConnectionType_Values(), false), + }, + "content_handling": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validIntegrationContentHandling(), }, - "credentials": { Type: schema.TypeString, Optional: true, ForceNew: true, }, - - "integration_http_method": { + "http_method": { Type: schema.TypeString, - Optional: true, + Required: true, ForceNew: true, ValidateFunc: validHTTPMethod(), }, - - "request_templates": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - - "request_parameters": { - Type: schema.TypeMap, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - }, - - "content_handling": { + "integration_http_method": { Type: schema.TypeString, Optional: true, - ValidateFunc: validIntegrationContentHandling(), + ForceNew: true, + ValidateFunc: validHTTPMethod(), }, - "passthrough_behavior": { Type: schema.TypeString, Optional: true, @@ -136,31 +98,35 @@ func ResourceIntegration() *schema.Resource { "NEVER", }, false), }, - - "cache_key_parameters": { - Type: schema.TypeSet, + "request_parameters": { + Type: schema.TypeMap, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, Optional: true, }, - - "cache_namespace": { - Type: schema.TypeString, + "request_templates": { + Type: schema.TypeMap, Optional: true, - Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "resource_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "rest_api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, - "timeout_milliseconds": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(50, 29000), Default: 29000, }, - "tls_config": { Type: schema.TypeList, Optional: true, - MinItems: 0, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -171,6 +137,16 @@ func ResourceIntegration() *schema.Resource { }, }, }, + "type": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.StringInSlice(apigateway.IntegrationType_Values(), false), + }, + "uri": { + Type: schema.TypeString, + Optional: true, + }, }, } } @@ -179,8 +155,6 @@ func resourceIntegrationCreate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Print("[DEBUG] Creating API Gateway Integration") - input := &apigateway.PutIntegrationInput{ HttpMethod: aws.String(d.Get("http_method").(string)), ResourceId: aws.String(d.Get("resource_id").(string)), @@ -245,7 +219,7 @@ func resourceIntegrationCreate(ctx context.Context, d *schema.ResourceData, meta _, err := conn.PutIntegrationWithContext(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "Error creating API Gateway Integration: %s", err) + return sdkdiag.AppendErrorf(diags, "creating API Gateway Integration: %s", err) } d.SetId(fmt.Sprintf("agi-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string))) @@ -257,25 +231,19 @@ func resourceIntegrationRead(ctx context.Context, d *schema.ResourceData, meta i var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Reading API Gateway Integration: %s", d.Id()) - integration, err := conn.GetIntegrationWithContext(ctx, &apigateway.GetIntegrationInput{ - HttpMethod: aws.String(d.Get("http_method").(string)), - ResourceId: aws.String(d.Get("resource_id").(string)), - RestApiId: aws.String(d.Get("rest_api_id").(string)), - }) + integration, err := FindIntegrationByThreePartKey(ctx, conn, d.Get("http_method").(string), d.Get("resource_id").(string), d.Get("rest_api_id").(string)) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway Integration (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway Integration (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } return sdkdiag.AppendErrorf(diags, "reading API Gateway Integration (%s): %s", d.Id(), err) } - log.Printf("[DEBUG] Received API Gateway Integration: %s", integration) - if err := d.Set("cache_key_parameters", flex.FlattenStringList(integration.CacheKeyParameters)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting cache_key_parameters: %s", err) - } + d.Set("cache_key_parameters", aws.StringValueSlice(integration.CacheKeyParameters)) d.Set("cache_namespace", integration.CacheNamespace) d.Set("connection_id", integration.ConnectionId) d.Set("connection_type", apigateway.ConnectionTypeInternet) @@ -286,20 +254,13 @@ func resourceIntegrationRead(ctx context.Context, d *schema.ResourceData, meta i d.Set("credentials", integration.Credentials) d.Set("integration_http_method", integration.HttpMethod) d.Set("passthrough_behavior", integration.PassthroughBehavior) - - if err := d.Set("request_parameters", aws.StringValueMap(integration.RequestParameters)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting request_parameters: %s", err) - } - + d.Set("request_parameters", aws.StringValueMap(integration.RequestParameters)) // We need to explicitly convert key = nil values into key = "", which aws.StringValueMap() removes - requestTemplateMap := make(map[string]string) - for key, valuePointer := range integration.RequestTemplates { - requestTemplateMap[key] = aws.StringValue(valuePointer) - } - if err := d.Set("request_templates", requestTemplateMap); err != nil { - return sdkdiag.AppendErrorf(diags, "setting request_templates: %s", err) + requestTemplates := make(map[string]string) + for k, v := range integration.RequestTemplates { + requestTemplates[k] = aws.StringValue(v) } - + d.Set("request_templates", requestTemplates) d.Set("timeout_milliseconds", integration.TimeoutInMillis) d.Set("type", integration.Type) d.Set("uri", integration.Uri) @@ -315,7 +276,6 @@ func resourceIntegrationUpdate(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Updating API Gateway Integration: %s", d.Id()) operations := make([]*apigateway.PatchOperation, 0) // https://docs.aws.amazon.com/apigateway/api-reference/link-relation/integration-update/#remarks @@ -484,28 +444,27 @@ func resourceIntegrationUpdate(ctx context.Context, d *schema.ResourceData, meta } } - params := &apigateway.UpdateIntegrationInput{ + input := &apigateway.UpdateIntegrationInput{ HttpMethod: aws.String(d.Get("http_method").(string)), + PatchOperations: operations, ResourceId: aws.String(d.Get("resource_id").(string)), RestApiId: aws.String(d.Get("rest_api_id").(string)), - PatchOperations: operations, } - _, err := conn.UpdateIntegrationWithContext(ctx, params) + _, err := conn.UpdateIntegrationWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Error updating API Gateway Integration: %s", err) + return sdkdiag.AppendErrorf(diags, "updating API Gateway Integration (%s): %s", d.Id(), err) } - d.SetId(fmt.Sprintf("agi-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string))) - return append(diags, resourceIntegrationRead(ctx, d, meta)...) } func resourceIntegrationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Deleting API Gateway Integration: %s", d.Id()) + log.Printf("[DEBUG] Deleting API Gateway Integration: %s", d.Id()) _, err := conn.DeleteIntegrationWithContext(ctx, &apigateway.DeleteIntegrationInput{ HttpMethod: aws.String(d.Get("http_method").(string)), ResourceId: aws.String(d.Get("resource_id").(string)), @@ -523,6 +482,33 @@ func resourceIntegrationDelete(ctx context.Context, d *schema.ResourceData, meta return diags } +func FindIntegrationByThreePartKey(ctx context.Context, conn *apigateway.APIGateway, httpMethod, resourceID, apiID string) (*apigateway.Integration, error) { + input := &apigateway.GetIntegrationInput{ + HttpMethod: aws.String(httpMethod), + ResourceId: aws.String(resourceID), + RestApiId: aws.String(apiID), + } + + output, err := conn.GetIntegrationWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} + func expandTLSConfig(vConfig []interface{}) *apigateway.TlsConfig { config := &apigateway.TlsConfig{} diff --git a/internal/service/apigateway/integration_response.go b/internal/service/apigateway/integration_response.go index 468617d22b9..73c60fc9431 100644 --- a/internal/service/apigateway/integration_response.go +++ b/internal/service/apigateway/integration_response.go @@ -10,17 +10,21 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceIntegrationResponse() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceIntegrationResponseCreate, + CreateWithoutTimeout: resourceIntegrationResponsePut, ReadWithoutTimeout: resourceIntegrationResponseRead, - UpdateWithoutTimeout: resourceIntegrationResponseCreate, + UpdateWithoutTimeout: resourceIntegrationResponsePut, DeleteWithoutTimeout: resourceIntegrationResponseDelete, + Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") @@ -41,97 +45,85 @@ func ResourceIntegrationResponse() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "rest_api_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "resource_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + "content_handling": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validIntegrationContentHandling(), }, - "http_method": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validHTTPMethod(), }, - - "status_code": { + "resource_id": { Type: schema.TypeString, Required: true, + ForceNew: true, }, - - "selection_pattern": { - Type: schema.TypeString, + "response_parameters": { + Type: schema.TypeMap, + Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, }, - "response_templates": { Type: schema.TypeMap, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - - "response_parameters": { - Type: schema.TypeMap, - Elem: &schema.Schema{Type: schema.TypeString}, + "rest_api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "selection_pattern": { + Type: schema.TypeString, Optional: true, }, - - "content_handling": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validIntegrationContentHandling(), + "status_code": { + Type: schema.TypeString, + Required: true, }, }, } } -func resourceIntegrationResponseCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceIntegrationResponsePut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - templates := make(map[string]string) - for k, v := range d.Get("response_templates").(map[string]interface{}) { - templates[k] = v.(string) + input := &apigateway.PutIntegrationResponseInput{ + HttpMethod: aws.String(d.Get("http_method").(string)), + ResourceId: aws.String(d.Get("resource_id").(string)), + RestApiId: aws.String(d.Get("rest_api_id").(string)), + StatusCode: aws.String(d.Get("status_code").(string)), } - parameters := make(map[string]string) - if kv, ok := d.GetOk("response_parameters"); ok { - for k, v := range kv.(map[string]interface{}) { - parameters[k] = v.(string) - } + if v, ok := d.GetOk("content_handling"); ok { + input.ContentHandling = aws.String(v.(string)) } - var contentHandling *string - if val, ok := d.GetOk("content_handling"); ok { - contentHandling = aws.String(val.(string)) + if v, ok := d.GetOk("response_parameters"); ok && len(v.(map[string]interface{})) > 0 { + input.ResponseParameters = flex.ExpandStringMap(v.(map[string]interface{})) } - input := apigateway.PutIntegrationResponseInput{ - HttpMethod: aws.String(d.Get("http_method").(string)), - ResourceId: aws.String(d.Get("resource_id").(string)), - RestApiId: aws.String(d.Get("rest_api_id").(string)), - StatusCode: aws.String(d.Get("status_code").(string)), - ResponseTemplates: aws.StringMap(templates), - ResponseParameters: aws.StringMap(parameters), - ContentHandling: contentHandling, + if v, ok := d.GetOk("response_templates"); ok && len(v.(map[string]interface{})) > 0 { + input.ResponseTemplates = flex.ExpandStringMap(v.(map[string]interface{})) } + if v, ok := d.GetOk("selection_pattern"); ok { input.SelectionPattern = aws.String(v.(string)) } - _, err := conn.PutIntegrationResponseWithContext(ctx, &input) + _, err := conn.PutIntegrationResponseWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Error creating API Gateway Integration Response: %s", err) + return sdkdiag.AppendErrorf(diags, "putting API Gateway Integration Response: %s", err) } - d.SetId(fmt.Sprintf("agir-%s-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string), d.Get("status_code").(string))) - log.Printf("[DEBUG] API Gateway Integration Response ID: %s", d.Id()) + if d.IsNewResource() { + d.SetId(fmt.Sprintf("agir-%s-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string), d.Get("status_code").(string))) + } return append(diags, resourceIntegrationResponseRead(ctx, d, meta)...) } @@ -140,39 +132,26 @@ func resourceIntegrationResponseRead(ctx context.Context, d *schema.ResourceData var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Reading API Gateway Integration Response %s", d.Id()) - integrationResponse, err := conn.GetIntegrationResponseWithContext(ctx, &apigateway.GetIntegrationResponseInput{ - HttpMethod: aws.String(d.Get("http_method").(string)), - ResourceId: aws.String(d.Get("resource_id").(string)), - RestApiId: aws.String(d.Get("rest_api_id").(string)), - StatusCode: aws.String(d.Get("status_code").(string)), - }) + integrationResponse, err := FindIntegrationResponseByFourPartKey(ctx, conn, d.Get("http_method").(string), d.Get("resource_id").(string), d.Get("rest_api_id").(string), d.Get("status_code").(string)) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway Integration Response (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway Integration Response (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } return sdkdiag.AppendErrorf(diags, "reading API Gateway Integration Response (%s): %s", d.Id(), err) } - log.Printf("[DEBUG] Received API Gateway Integration Response: %s", integrationResponse) - d.Set("content_handling", integrationResponse.ContentHandling) - - if err := d.Set("response_parameters", aws.StringValueMap(integrationResponse.ResponseParameters)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting response_parameters: %s", err) - } - - // We need to explicitly convert key = nil values into key = "", which aws.StringValueMap() removes - responseTemplateMap := make(map[string]string) - for key, valuePointer := range integrationResponse.ResponseTemplates { - responseTemplateMap[key] = aws.StringValue(valuePointer) + d.Set("response_parameters", aws.StringValueMap(integrationResponse.ResponseParameters)) + // We need to explicitly convert key = nil values into key = "", which aws.StringValueMap() removes. + responseTemplates := make(map[string]string) + for k, v := range integrationResponse.ResponseTemplates { + responseTemplates[k] = aws.StringValue(v) } - if err := d.Set("response_templates", responseTemplateMap); err != nil { - return sdkdiag.AppendErrorf(diags, "setting response_templates: %s", err) - } - + d.Set("response_templates", responseTemplates) d.Set("selection_pattern", integrationResponse.SelectionPattern) return diags @@ -181,8 +160,8 @@ func resourceIntegrationResponseRead(ctx context.Context, d *schema.ResourceData func resourceIntegrationResponseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Deleting API Gateway Integration Response: %s", d.Id()) + log.Printf("[DEBUG] Deleting API Gateway Integration Response: %s", d.Id()) _, err := conn.DeleteIntegrationResponseWithContext(ctx, &apigateway.DeleteIntegrationResponseInput{ HttpMethod: aws.String(d.Get("http_method").(string)), ResourceId: aws.String(d.Get("resource_id").(string)), @@ -200,3 +179,31 @@ func resourceIntegrationResponseDelete(ctx context.Context, d *schema.ResourceDa return diags } + +func FindIntegrationResponseByFourPartKey(ctx context.Context, conn *apigateway.APIGateway, httpMethod, resourceID, apiID, statusCode string) (*apigateway.IntegrationResponse, error) { + input := &apigateway.GetIntegrationResponseInput{ + HttpMethod: aws.String(httpMethod), + ResourceId: aws.String(resourceID), + RestApiId: aws.String(apiID), + StatusCode: aws.String(statusCode), + } + + output, err := conn.GetIntegrationResponseWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/apigateway/integration_response_test.go b/internal/service/apigateway/integration_response_test.go index da7b3a6c254..e6540c90d57 100644 --- a/internal/service/apigateway/integration_response_test.go +++ b/internal/service/apigateway/integration_response_test.go @@ -5,8 +5,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,12 +12,13 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayIntegrationResponse_basic(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.IntegrationResponse - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration_response.test" resource.ParallelTest(t, resource.TestCase{ @@ -30,29 +29,16 @@ func TestAccAPIGatewayIntegrationResponse_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccIntegrationResponseConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckIntegrationResponseExists(ctx, resourceName, &conf), - testAccCheckIntegrationResponseAttributes(&conf), - resource.TestCheckResourceAttr( - resourceName, "response_templates.application/json", ""), - resource.TestCheckResourceAttr( - resourceName, "response_templates.application/xml", "#set($inputRoot = $input.path('$'))\n{ }"), - resource.TestCheckResourceAttr( - resourceName, "content_handling", ""), - ), - }, - - { - Config: testAccIntegrationResponseConfig_update(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckIntegrationResponseExists(ctx, resourceName, &conf), - testAccCheckIntegrationResponseAttributesUpdate(&conf), - resource.TestCheckResourceAttr( - resourceName, "response_templates.application/json", "$input.path('$')"), - resource.TestCheckResourceAttr( - resourceName, "response_templates.application/xml", ""), - resource.TestCheckResourceAttr( - resourceName, "content_handling", "CONVERT_TO_BINARY"), + resource.TestCheckResourceAttr(resourceName, "content_handling", ""), + resource.TestCheckResourceAttr(resourceName, "response_parameters.%", "1"), + resource.TestCheckResourceAttr(resourceName, "response_parameters.method.response.header.Content-Type", "integration.response.body.type"), + resource.TestCheckResourceAttr(resourceName, "response_templates.%", "2"), + resource.TestCheckResourceAttr(resourceName, "response_templates.application/json", ""), + resource.TestCheckResourceAttr(resourceName, "response_templates.application/xml", "#set($inputRoot = $input.path('$'))\n{ }"), + resource.TestCheckResourceAttr(resourceName, "selection_pattern", ".*"), + resource.TestCheckResourceAttr(resourceName, "status_code", "400"), ), }, { @@ -61,6 +47,19 @@ func TestAccAPIGatewayIntegrationResponse_basic(t *testing.T) { ImportStateIdFunc: testAccIntegrationResponseImportStateIdFunc(resourceName), ImportStateVerify: true, }, + { + Config: testAccIntegrationResponseConfig_update(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIntegrationResponseExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "content_handling", "CONVERT_TO_BINARY"), + resource.TestCheckResourceAttr(resourceName, "response_parameters.%", "0"), + resource.TestCheckResourceAttr(resourceName, "response_templates.%", "2"), + resource.TestCheckResourceAttr(resourceName, "response_templates.application/json", "$input.path('$')"), + resource.TestCheckResourceAttr(resourceName, "response_templates.application/xml", ""), + resource.TestCheckResourceAttr(resourceName, "selection_pattern", ""), + resource.TestCheckResourceAttr(resourceName, "status_code", "400"), + ), + }, }, }) } @@ -68,7 +67,7 @@ func TestAccAPIGatewayIntegrationResponse_basic(t *testing.T) { func TestAccAPIGatewayIntegrationResponse_disappears(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.IntegrationResponse - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(10)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration_response.test" resource.ParallelTest(t, resource.TestCase{ @@ -89,50 +88,7 @@ func TestAccAPIGatewayIntegrationResponse_disappears(t *testing.T) { }) } -func testAccCheckIntegrationResponseAttributes(conf *apigateway.IntegrationResponse) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *conf.StatusCode != "400" { - return fmt.Errorf("wrong StatusCode: %q", *conf.StatusCode) - } - if conf.ResponseTemplates["application/json"] != nil { - return fmt.Errorf("wrong ResponseTemplate for application/json") - } - if *conf.ResponseTemplates["application/xml"] != "#set($inputRoot = $input.path('$'))\n{ }" { - return fmt.Errorf("wrong ResponseTemplate for application/xml") - } - if conf.SelectionPattern == nil || *conf.SelectionPattern != ".*" { - return fmt.Errorf("wrong SelectionPattern (expected .*)") - } - if *conf.ResponseParameters["method.response.header.Content-Type"] != "integration.response.body.type" { - return fmt.Errorf("wrong ResponseParameters for header.Content-Type") - } - return nil - } -} - -func testAccCheckIntegrationResponseAttributesUpdate(conf *apigateway.IntegrationResponse) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *conf.StatusCode != "400" { - return fmt.Errorf("wrong StatusCode: %q", *conf.StatusCode) - } - if *conf.ResponseTemplates["application/json"] != "$input.path('$')" { - return fmt.Errorf("wrong ResponseTemplate for application/json") - } - if conf.ResponseTemplates["application/xml"] != nil { - return fmt.Errorf("wrong ResponseTemplate for application/xml") - } - if conf.SelectionPattern != nil { - return fmt.Errorf("wrong SelectionPattern (expected nil)") - } - if conf.ResponseParameters["method.response.header.Content-Type"] != nil { - return fmt.Errorf("ResponseParameters for header.Content-Type shouldnt exist") - } - - return nil - } -} - -func testAccCheckIntegrationResponseExists(ctx context.Context, n string, res *apigateway.IntegrationResponse) resource.TestCheckFunc { +func testAccCheckIntegrationResponseExists(ctx context.Context, n string, v *apigateway.IntegrationResponse) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -140,23 +96,18 @@ func testAccCheckIntegrationResponseExists(ctx context.Context, n string, res *a } if rs.Primary.ID == "" { - return fmt.Errorf("No API Gateway Method ID is set") + return fmt.Errorf("No API Gateway Integration Response ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetIntegrationResponseInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - StatusCode: aws.String(rs.Primary.Attributes["status_code"]), - } - describe, err := conn.GetIntegrationResponseWithContext(ctx, req) + output, err := tfapigateway.FindIntegrationResponseByFourPartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"], rs.Primary.Attributes["status_code"]) + if err != nil { return err } - *res = *describe + *v = *output return nil } @@ -171,27 +122,17 @@ func testAccCheckIntegrationResponseDestroy(ctx context.Context) resource.TestCh continue } - req := &apigateway.GetIntegrationResponseInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - StatusCode: aws.String(rs.Primary.Attributes["status_code"]), - } - _, err := conn.GetIntegrationResponseWithContext(ctx, req) + _, err := tfapigateway.FindIntegrationResponseByFourPartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"], rs.Primary.Attributes["status_code"]) - if err == nil { - return fmt.Errorf("API Gateway Method still exists") + if tfresource.NotFound(err) { + continue } - aws2err, ok := err.(awserr.Error) - if !ok { - return err - } - if aws2err.Code() != "NotFoundException" { + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway Integration Response %s still exists", rs.Primary.ID) } return nil @@ -212,7 +153,7 @@ func testAccIntegrationResponseImportStateIdFunc(resourceName string) resource.I func testAccIntegrationResponseConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { - name = "%s" + name = %[1]q } resource "aws_api_gateway_resource" "test" { @@ -282,7 +223,7 @@ resource "aws_api_gateway_integration_response" "test" { func testAccIntegrationResponseConfig_update(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { - name = "%s" + name = %[1]q } resource "aws_api_gateway_resource" "test" { diff --git a/internal/service/apigateway/integration_test.go b/internal/service/apigateway/integration_test.go index cc31bde42bb..f5490430875 100644 --- a/internal/service/apigateway/integration_test.go +++ b/internal/service/apigateway/integration_test.go @@ -6,8 +6,6 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -15,12 +13,13 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayIntegration_basic(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Integration - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(7)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration.test" resource.ParallelTest(t, resource.TestCase{ @@ -49,7 +48,12 @@ func TestAccAPIGatewayIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "tls_config.#", "0"), ), }, - + { + ResourceName: resourceName, + ImportState: true, + ImportStateIdFunc: testAccIntegrationImportStateIdFunc(resourceName), + ImportStateVerify: true, + }, { Config: testAccIntegrationConfig_update(rName), Check: resource.ComposeTestCheckFunc( @@ -69,7 +73,6 @@ func TestAccAPIGatewayIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "2000"), ), }, - { Config: testAccIntegrationConfig_updateURI(rName), Check: resource.ComposeTestCheckFunc( @@ -89,7 +92,6 @@ func TestAccAPIGatewayIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "2000"), ), }, - { Config: testAccIntegrationConfig_updateNoTemplates(rName), Check: resource.ComposeTestCheckFunc( @@ -105,7 +107,6 @@ func TestAccAPIGatewayIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "2000"), ), }, - { Config: testAccIntegrationConfig_basic(rName), Check: resource.ComposeTestCheckFunc( @@ -124,12 +125,6 @@ func TestAccAPIGatewayIntegration_basic(t *testing.T) { resource.TestCheckResourceAttr(resourceName, "timeout_milliseconds", "29000"), ), }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateIdFunc: testAccIntegrationImportStateIdFunc(resourceName), - ImportStateVerify: true, - }, }, }) } @@ -137,7 +132,7 @@ func TestAccAPIGatewayIntegration_basic(t *testing.T) { func TestAccAPIGatewayIntegration_contentHandling(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Integration - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(7)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration.test" resource.ParallelTest(t, resource.TestCase{ @@ -214,7 +209,7 @@ func TestAccAPIGatewayIntegration_contentHandling(t *testing.T) { func TestAccAPIGatewayIntegration_CacheKey_parameters(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Integration - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(7)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration.test" resource.ParallelTest(t, resource.TestCase{ @@ -258,7 +253,7 @@ func TestAccAPIGatewayIntegration_CacheKey_parameters(t *testing.T) { func TestAccAPIGatewayIntegration_integrationType(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Integration - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(7)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration.test" resource.ParallelTest(t, resource.TestCase{ @@ -304,7 +299,7 @@ func TestAccAPIGatewayIntegration_integrationType(t *testing.T) { func TestAccAPIGatewayIntegration_TLS_insecureSkipVerification(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Integration - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(7)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration.test" resource.ParallelTest(t, resource.TestCase{ @@ -342,7 +337,7 @@ func TestAccAPIGatewayIntegration_TLS_insecureSkipVerification(t *testing.T) { func TestAccAPIGatewayIntegration_disappears(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Integration - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(7)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_integration.test" resource.ParallelTest(t, resource.TestCase{ @@ -363,7 +358,7 @@ func TestAccAPIGatewayIntegration_disappears(t *testing.T) { }) } -func testAccCheckIntegrationExists(ctx context.Context, n string, res *apigateway.Integration) resource.TestCheckFunc { +func testAccCheckIntegrationExists(ctx context.Context, n string, v *apigateway.Integration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -371,22 +366,18 @@ func testAccCheckIntegrationExists(ctx context.Context, n string, res *apigatewa } if rs.Primary.ID == "" { - return fmt.Errorf("No API Gateway Method ID is set") + return fmt.Errorf("No API Gateway Integration ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetIntegrationInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - } - describe, err := conn.GetIntegrationWithContext(ctx, req) + output, err := tfapigateway.FindIntegrationByThreePartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"]) + if err != nil { return err } - *res = *describe + *v = *output return nil } @@ -401,26 +392,17 @@ func testAccCheckIntegrationDestroy(ctx context.Context) resource.TestCheckFunc continue } - req := &apigateway.GetIntegrationInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - } - _, err := conn.GetIntegrationWithContext(ctx, req) + _, err := tfapigateway.FindIntegrationByThreePartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"]) - if err == nil { - return fmt.Errorf("API Gateway Method still exists") + if tfresource.NotFound(err) { + continue } - aws2err, ok := err.(awserr.Error) - if !ok { - return err - } - if aws2err.Code() != "NotFoundException" { + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway Integration %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/apigateway/method.go b/internal/service/apigateway/method.go index 58ab67567c0..bb471ab9220 100644 --- a/internal/service/apigateway/method.go +++ b/internal/service/apigateway/method.go @@ -4,17 +4,18 @@ import ( "context" "fmt" "log" - "strconv" "strings" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceMethod() *schema.Resource { @@ -23,6 +24,7 @@ func ResourceMethod() *schema.Resource { ReadWithoutTimeout: resourceMethodRead, UpdateWithoutTimeout: resourceMethodUpdate, DeleteWithoutTimeout: resourceMethodDelete, + Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") @@ -41,68 +43,57 @@ func ResourceMethod() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "rest_api_id": { + "api_key_required": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "authorization": { Type: schema.TypeString, Required: true, - ForceNew: true, }, - - "resource_id": { + "authorization_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "authorizer_id": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Optional: true, }, - "http_method": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validHTTPMethod(), }, - - "authorization": { - Type: schema.TypeString, - Required: true, - }, - - "authorizer_id": { + "operation_name": { Type: schema.TypeString, Optional: true, }, - - "authorization_scopes": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, - Optional: true, - }, - - "api_key_required": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "request_models": { Type: schema.TypeMap, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "request_parameters": { Type: schema.TypeMap, Elem: &schema.Schema{Type: schema.TypeBool}, Optional: true, }, - "request_validator_id": { Type: schema.TypeString, Optional: true, }, - - "operation_name": { + "resource_id": { Type: schema.TypeString, - Optional: true, + Required: true, + ForceNew: true, + }, + "rest_api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, }, } @@ -112,32 +103,12 @@ func resourceMethodCreate(ctx context.Context, d *schema.ResourceData, meta inte var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - input := apigateway.PutMethodInput{ + input := &apigateway.PutMethodInput{ + ApiKeyRequired: aws.Bool(d.Get("api_key_required").(bool)), AuthorizationType: aws.String(d.Get("authorization").(string)), HttpMethod: aws.String(d.Get("http_method").(string)), ResourceId: aws.String(d.Get("resource_id").(string)), RestApiId: aws.String(d.Get("rest_api_id").(string)), - ApiKeyRequired: aws.Bool(d.Get("api_key_required").(bool)), - } - - models := make(map[string]string) - for k, v := range d.Get("request_models").(map[string]interface{}) { - models[k] = v.(string) - } - if len(models) > 0 { - input.RequestModels = aws.StringMap(models) - } - - parameters := make(map[string]bool) - if kv, ok := d.GetOk("request_parameters"); ok { - for k, v := range kv.(map[string]interface{}) { - parameters[k], ok = v.(bool) - if !ok { - value, _ := strconv.ParseBool(v.(string)) - parameters[k] = value - } - } - input.RequestParameters = aws.BoolMap(parameters) } if v, ok := d.GetOk("authorizer_id"); ok { @@ -152,17 +123,25 @@ func resourceMethodCreate(ctx context.Context, d *schema.ResourceData, meta inte input.OperationName = aws.String(v.(string)) } + if v, ok := d.GetOk("request_models"); ok && len(v.(map[string]interface{})) > 0 { + input.RequestModels = flex.ExpandStringMap(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("request_parameters"); ok && len(v.(map[string]interface{})) > 0 { + input.RequestParameters = flex.ExpandBoolMap(v.(map[string]interface{})) + } + if v, ok := d.GetOk("request_validator_id"); ok { input.RequestValidatorId = aws.String(v.(string)) } - _, err := conn.PutMethodWithContext(ctx, &input) + _, err := conn.PutMethodWithContext(ctx, input) + if err != nil { return sdkdiag.AppendErrorf(diags, "creating API Gateway Method: %s", err) } d.SetId(fmt.Sprintf("agm-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string))) - log.Printf("[DEBUG] API Gateway Method ID: %s", d.Id()) return diags } @@ -171,41 +150,26 @@ func resourceMethodRead(ctx context.Context, d *schema.ResourceData, meta interf var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Reading API Gateway Method %s", d.Id()) - out, err := conn.GetMethodWithContext(ctx, &apigateway.GetMethodInput{ - HttpMethod: aws.String(d.Get("http_method").(string)), - ResourceId: aws.String(d.Get("resource_id").(string)), - RestApiId: aws.String(d.Get("rest_api_id").(string)), - }) - if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway Method (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "reading API Gateway Method (%s): %s", d.Id(), err) - } - log.Printf("[DEBUG] Received API Gateway Method: %s", out) - - d.Set("api_key_required", out.ApiKeyRequired) - - if err := d.Set("authorization_scopes", flex.FlattenStringList(out.AuthorizationScopes)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting authorization_scopes: %s", err) - } - - d.Set("authorization", out.AuthorizationType) - d.Set("authorizer_id", out.AuthorizerId) - d.Set("operation_name", out.OperationName) + method, err := FindMethodByThreePartKey(ctx, conn, d.Get("http_method").(string), d.Get("resource_id").(string), d.Get("rest_api_id").(string)) - if err := d.Set("request_models", aws.StringValueMap(out.RequestModels)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting request_models: %s", err) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway Method (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - if err := d.Set("request_parameters", aws.BoolValueMap(out.RequestParameters)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting request_parameters: %s", err) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading API Gateway Method (%s): %s", d.Id(), err) } - d.Set("request_validator_id", out.RequestValidatorId) + d.Set("api_key_required", method.ApiKeyRequired) + d.Set("authorization", method.AuthorizationType) + d.Set("authorization_scopes", aws.StringValueSlice(method.AuthorizationScopes)) + d.Set("authorizer_id", method.AuthorizerId) + d.Set("operation_name", method.OperationName) + d.Set("request_models", aws.StringValueMap(method.RequestModels)) + d.Set("request_parameters", aws.BoolValueMap(method.RequestParameters)) + d.Set("request_validator_id", method.RequestValidatorId) return diags } @@ -214,8 +178,8 @@ func resourceMethodUpdate(ctx context.Context, d *schema.ResourceData, meta inte var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Updating API Gateway Method %s", d.Id()) operations := make([]*apigateway.PatchOperation, 0) + if d.HasChange("resource_id") { operations = append(operations, &apigateway.PatchOperation{ Op: aws.String(apigateway.OpReplace), @@ -229,15 +193,6 @@ func resourceMethodUpdate(ctx context.Context, d *schema.ResourceData, meta inte } if d.HasChange("request_parameters") { - parameters := make(map[string]bool) - var ok bool - for k, v := range d.Get("request_parameters").(map[string]interface{}) { - parameters[k], ok = v.(bool) - if !ok { - value, _ := strconv.ParseBool(v.(string)) - parameters[k] = value - } - } ops := expandMethodParametersOperations(d, "request_parameters", "requestParameters") operations = append(operations, ops...) } @@ -322,12 +277,14 @@ func resourceMethodUpdate(ctx context.Context, d *schema.ResourceData, meta inte }) } - _, err := conn.UpdateMethodWithContext(ctx, &apigateway.UpdateMethodInput{ + input := &apigateway.UpdateMethodInput{ HttpMethod: aws.String(d.Get("http_method").(string)), + PatchOperations: operations, ResourceId: aws.String(d.Get("resource_id").(string)), RestApiId: aws.String(d.Get("rest_api_id").(string)), - PatchOperations: operations, - }) + } + + _, err := conn.UpdateMethodWithContext(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating API Gateway Method (%s): %s", d.Id(), err) @@ -339,8 +296,8 @@ func resourceMethodUpdate(ctx context.Context, d *schema.ResourceData, meta inte func resourceMethodDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Deleting API Gateway Method: %s", d.Id()) + log.Printf("[DEBUG] Deleting API Gateway Method: %s", d.Id()) _, err := conn.DeleteMethodWithContext(ctx, &apigateway.DeleteMethodInput{ HttpMethod: aws.String(d.Get("http_method").(string)), ResourceId: aws.String(d.Get("resource_id").(string)), @@ -357,3 +314,30 @@ func resourceMethodDelete(ctx context.Context, d *schema.ResourceData, meta inte return diags } + +func FindMethodByThreePartKey(ctx context.Context, conn *apigateway.APIGateway, httpMethod, resourceID, apiID string) (*apigateway.Method, error) { + input := &apigateway.GetMethodInput{ + HttpMethod: aws.String(httpMethod), + ResourceId: aws.String(resourceID), + RestApiId: aws.String(apiID), + } + + output, err := conn.GetMethodWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/apigateway/method_response.go b/internal/service/apigateway/method_response.go index de5f0d28091..9d1b2af94da 100644 --- a/internal/service/apigateway/method_response.go +++ b/internal/service/apigateway/method_response.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "log" - "strconv" "strings" "sync" "time" @@ -13,9 +12,11 @@ import ( "github.com/aws/aws-sdk-go/service/apigateway" "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) @@ -27,6 +28,7 @@ func ResourceMethodResponse() *schema.Resource { ReadWithoutTimeout: resourceMethodResponseRead, UpdateWithoutTimeout: resourceMethodResponseUpdate, DeleteWithoutTimeout: resourceMethodResponseDelete, + Importer: &schema.ResourceImporter{ StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") @@ -47,41 +49,36 @@ func ResourceMethodResponse() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "rest_api_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "resource_id": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "http_method": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: validHTTPMethod(), }, - - "status_code": { + "resource_id": { Type: schema.TypeString, Required: true, + ForceNew: true, }, - "response_models": { Type: schema.TypeMap, Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, }, - "response_parameters": { Type: schema.TypeMap, Elem: &schema.Schema{Type: schema.TypeBool}, Optional: true, }, + "rest_api_id": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "status_code": { + Type: schema.TypeString, + Required: true, + }, }, } } @@ -90,34 +87,26 @@ func resourceMethodResponseCreate(ctx context.Context, d *schema.ResourceData, m var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - models := make(map[string]string) - for k, v := range d.Get("response_models").(map[string]interface{}) { - models[k] = v.(string) + input := &apigateway.PutMethodResponseInput{ + HttpMethod: aws.String(d.Get("http_method").(string)), + ResourceId: aws.String(d.Get("resource_id").(string)), + RestApiId: aws.String(d.Get("rest_api_id").(string)), + StatusCode: aws.String(d.Get("status_code").(string)), } - parameters := make(map[string]bool) - if kv, ok := d.GetOk("response_parameters"); ok { - for k, v := range kv.(map[string]interface{}) { - parameters[k], ok = v.(bool) - if !ok { - value, _ := strconv.ParseBool(v.(string)) - parameters[k] = value - } - } + if v, ok := d.GetOk("response_models"); ok && len(v.(map[string]interface{})) > 0 { + input.ResponseModels = flex.ExpandStringMap(v.(map[string]interface{})) + } + + if v, ok := d.GetOk("response_parameters"); ok && len(v.(map[string]interface{})) > 0 { + input.ResponseParameters = flex.ExpandBoolMap(v.(map[string]interface{})) } resourceMethodResponseMutex.Lock() defer resourceMethodResponseMutex.Unlock() _, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, 2*time.Minute, func() (interface{}, error) { - return conn.PutMethodResponseWithContext(ctx, &apigateway.PutMethodResponseInput{ - HttpMethod: aws.String(d.Get("http_method").(string)), - ResourceId: aws.String(d.Get("resource_id").(string)), - RestApiId: aws.String(d.Get("rest_api_id").(string)), - StatusCode: aws.String(d.Get("status_code").(string)), - ResponseModels: aws.StringMap(models), - ResponseParameters: aws.BoolMap(parameters), - }) + return conn.PutMethodResponseWithContext(ctx, input) }, apigateway.ErrCodeConflictException) if err != nil { @@ -125,7 +114,6 @@ func resourceMethodResponseCreate(ctx context.Context, d *schema.ResourceData, m } d.SetId(fmt.Sprintf("agmr-%s-%s-%s-%s", d.Get("rest_api_id").(string), d.Get("resource_id").(string), d.Get("http_method").(string), d.Get("status_code").(string))) - log.Printf("[DEBUG] API Gateway Method ID: %s", d.Id()) return diags } @@ -134,19 +122,15 @@ func resourceMethodResponseRead(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Reading API Gateway Method Response %s", d.Id()) - methodResponse, err := conn.GetMethodResponseWithContext(ctx, &apigateway.GetMethodResponseInput{ - HttpMethod: aws.String(d.Get("http_method").(string)), - ResourceId: aws.String(d.Get("resource_id").(string)), - RestApiId: aws.String(d.Get("rest_api_id").(string)), - StatusCode: aws.String(d.Get("status_code").(string)), - }) + methodResponse, err := FindMethodResponseByFourPartKey(ctx, conn, d.Get("http_method").(string), d.Get("resource_id").(string), d.Get("rest_api_id").(string), d.Get("status_code").(string)) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] API Gateway Method Response (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { - log.Printf("[WARN] API Gateway Method Response (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } return sdkdiag.AppendErrorf(diags, "reading API Gateway Method Response (%s): %s", d.Id(), err) } @@ -167,7 +151,6 @@ func resourceMethodResponseUpdate(ctx context.Context, d *schema.ResourceData, m var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Updating API Gateway Method Response %s", d.Id()) operations := make([]*apigateway.PatchOperation, 0) if d.HasChange("response_models") { @@ -175,17 +158,18 @@ func resourceMethodResponseUpdate(ctx context.Context, d *schema.ResourceData, m } if d.HasChange("response_parameters") { - ops := expandMethodParametersOperations(d, "response_parameters", "responseParameters") - operations = append(operations, ops...) + operations = append(operations, expandMethodParametersOperations(d, "response_parameters", "responseParameters")...) } - _, err := conn.UpdateMethodResponseWithContext(ctx, &apigateway.UpdateMethodResponseInput{ + input := &apigateway.UpdateMethodResponseInput{ HttpMethod: aws.String(d.Get("http_method").(string)), + PatchOperations: operations, ResourceId: aws.String(d.Get("resource_id").(string)), RestApiId: aws.String(d.Get("rest_api_id").(string)), StatusCode: aws.String(d.Get("status_code").(string)), - PatchOperations: operations, - }) + } + + _, err := conn.UpdateMethodResponseWithContext(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating API Gateway Method Response (%s): %s", d.Id(), err) @@ -197,8 +181,8 @@ func resourceMethodResponseUpdate(ctx context.Context, d *schema.ResourceData, m func resourceMethodResponseDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).APIGatewayConn() - log.Printf("[DEBUG] Deleting API Gateway Method Response: %s", d.Id()) + log.Printf("[DEBUG] Deleting API Gateway Method Response: %s", d.Id()) _, err := conn.DeleteMethodResponseWithContext(ctx, &apigateway.DeleteMethodResponseInput{ HttpMethod: aws.String(d.Get("http_method").(string)), ResourceId: aws.String(d.Get("resource_id").(string)), @@ -216,3 +200,31 @@ func resourceMethodResponseDelete(ctx context.Context, d *schema.ResourceData, m return diags } + +func FindMethodResponseByFourPartKey(ctx context.Context, conn *apigateway.APIGateway, httpMethod, resourceID, apiID, statusCode string) (*apigateway.MethodResponse, error) { + input := &apigateway.GetMethodResponseInput{ + HttpMethod: aws.String(httpMethod), + ResourceId: aws.String(resourceID), + RestApiId: aws.String(apiID), + StatusCode: aws.String(statusCode), + } + + output, err := conn.GetMethodResponseWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, apigateway.ErrCodeNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/apigateway/method_response_test.go b/internal/service/apigateway/method_response_test.go index 02bcca99a3d..836ffefe497 100644 --- a/internal/service/apigateway/method_response_test.go +++ b/internal/service/apigateway/method_response_test.go @@ -5,8 +5,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,13 +12,14 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayMethodResponse_basic(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.MethodResponse - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(10)) - resourceName := "aws_api_gateway_method_response.error" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_api_gateway_method_response.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckAPIGatewayTypeEDGE(t) }, @@ -30,25 +29,14 @@ func TestAccAPIGatewayMethodResponse_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccMethodResponseConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckMethodResponseExists(ctx, resourceName, &conf), - testAccCheckMethodResponseAttributes(&conf), - resource.TestCheckResourceAttr( - resourceName, "status_code", "400"), - resource.TestCheckResourceAttr( - resourceName, "response_models.application/json", "Error"), - ), - }, - - { - Config: testAccMethodResponseConfig_update(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckMethodResponseExists(ctx, resourceName, &conf), - testAccCheckMethodResponseAttributesUpdate(&conf), - resource.TestCheckResourceAttr( - resourceName, "status_code", "400"), - resource.TestCheckResourceAttr( - resourceName, "response_models.application/json", "Empty"), + resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), + resource.TestCheckResourceAttr(resourceName, "response_models.%", "1"), + resource.TestCheckResourceAttr(resourceName, "response_models.application/json", "Error"), + resource.TestCheckResourceAttr(resourceName, "response_parameters.%", "1"), + resource.TestCheckResourceAttr(resourceName, "response_parameters.method.response.header.Content-Type", "true"), + resource.TestCheckResourceAttr(resourceName, "status_code", "400"), ), }, { @@ -57,6 +45,18 @@ func TestAccAPIGatewayMethodResponse_basic(t *testing.T) { ImportStateIdFunc: testAccMethodResponseImportStateIdFunc(resourceName), ImportStateVerify: true, }, + { + Config: testAccMethodResponseConfig_update(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckMethodResponseExists(ctx, resourceName, &conf), + resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), + resource.TestCheckResourceAttr(resourceName, "response_models.%", "1"), + resource.TestCheckResourceAttr(resourceName, "response_models.application/json", "Empty"), + resource.TestCheckResourceAttr(resourceName, "response_parameters.%", "1"), + resource.TestCheckResourceAttr(resourceName, "response_parameters.method.response.header.Host", "false"), + resource.TestCheckResourceAttr(resourceName, "status_code", "400"), + ), + }, }, }) } @@ -64,8 +64,8 @@ func TestAccAPIGatewayMethodResponse_basic(t *testing.T) { func TestAccAPIGatewayMethodResponse_disappears(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.MethodResponse - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(10)) - resourceName := "aws_api_gateway_method_response.error" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_api_gateway_method_response.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckAPIGatewayTypeEDGE(t) }, @@ -85,49 +85,7 @@ func TestAccAPIGatewayMethodResponse_disappears(t *testing.T) { }) } -func testAccCheckMethodResponseAttributes(conf *apigateway.MethodResponse) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *conf.StatusCode == "" { - return fmt.Errorf("empty StatusCode") - } - if val, ok := conf.ResponseModels["application/json"]; !ok { - return fmt.Errorf("missing application/json ResponseModel") - } else { - if *val != "Error" { - return fmt.Errorf("wrong application/json ResponseModel") - } - } - if val, ok := conf.ResponseParameters["method.response.header.Content-Type"]; !ok { - return fmt.Errorf("missing Content-Type ResponseParameters") - } else { - if !*val { - return fmt.Errorf("wrong ResponseParameters value") - } - } - return nil - } -} - -func testAccCheckMethodResponseAttributesUpdate(conf *apigateway.MethodResponse) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *conf.StatusCode == "" { - return fmt.Errorf("empty StatusCode") - } - if val, ok := conf.ResponseModels["application/json"]; !ok { - return fmt.Errorf("missing application/json ResponseModel") - } else { - if *val != "Empty" { - return fmt.Errorf("wrong application/json ResponseModel") - } - } - if conf.ResponseParameters["method.response.header.Content-Type"] != nil { - return fmt.Errorf("Content-Type ResponseParameters shouldn't exist") - } - return nil - } -} - -func testAccCheckMethodResponseExists(ctx context.Context, n string, res *apigateway.MethodResponse) resource.TestCheckFunc { +func testAccCheckMethodResponseExists(ctx context.Context, n string, v *apigateway.MethodResponse) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -135,23 +93,18 @@ func testAccCheckMethodResponseExists(ctx context.Context, n string, res *apigat } if rs.Primary.ID == "" { - return fmt.Errorf("No API Gateway Method ID is set") + return fmt.Errorf("No API Gateway Method Response ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetMethodResponseInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - StatusCode: aws.String(rs.Primary.Attributes["status_code"]), - } - describe, err := conn.GetMethodResponseWithContext(ctx, req) + output, err := tfapigateway.FindMethodResponseByFourPartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"], rs.Primary.Attributes["status_code"]) + if err != nil { return err } - *res = *describe + *v = *output return nil } @@ -166,27 +119,17 @@ func testAccCheckMethodResponseDestroy(ctx context.Context) resource.TestCheckFu continue } - req := &apigateway.GetMethodResponseInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - StatusCode: aws.String(rs.Primary.Attributes["status_code"]), - } - _, err := conn.GetMethodResponseWithContext(ctx, req) + _, err := tfapigateway.FindMethodResponseByFourPartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"], rs.Primary.Attributes["status_code"]) - if err == nil { - return fmt.Errorf("API Gateway Method still exists") + if tfresource.NotFound(err) { + continue } - aws2err, ok := err.(awserr.Error) - if !ok { - return err - } - if aws2err.Code() != "NotFoundException" { + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway Method Response %s still exists", rs.Primary.ID) } return nil @@ -207,7 +150,7 @@ func testAccMethodResponseImportStateIdFunc(resourceName string) resource.Import func testAccMethodResponseConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { - name = "%s" + name = %[1]q } resource "aws_api_gateway_resource" "test" { @@ -227,7 +170,7 @@ resource "aws_api_gateway_method" "test" { } } -resource "aws_api_gateway_method_response" "error" { +resource "aws_api_gateway_method_response" "test" { rest_api_id = aws_api_gateway_rest_api.test.id resource_id = aws_api_gateway_resource.test.id http_method = aws_api_gateway_method.test.http_method @@ -247,7 +190,7 @@ resource "aws_api_gateway_method_response" "error" { func testAccMethodResponseConfig_update(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { - name = "%s" + name = %[1]q } resource "aws_api_gateway_resource" "test" { @@ -267,7 +210,7 @@ resource "aws_api_gateway_method" "test" { } } -resource "aws_api_gateway_method_response" "error" { +resource "aws_api_gateway_method_response" "test" { rest_api_id = aws_api_gateway_rest_api.test.id resource_id = aws_api_gateway_resource.test.id http_method = aws_api_gateway_method.test.http_method @@ -278,7 +221,7 @@ resource "aws_api_gateway_method_response" "error" { } response_parameters = { - "method.response.header.Host" = true + "method.response.header.Host" = false } } `, rName) diff --git a/internal/service/apigateway/method_test.go b/internal/service/apigateway/method_test.go index 59161ea6e83..8fba5aad4aa 100644 --- a/internal/service/apigateway/method_test.go +++ b/internal/service/apigateway/method_test.go @@ -3,11 +3,8 @@ package apigateway_test import ( "context" "fmt" - "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/apigateway" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -15,12 +12,13 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfapigateway "github.com/hashicorp/terraform-provider-aws/internal/service/apigateway" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccAPIGatewayMethod_basic(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Method - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_method.test" resource.ParallelTest(t, resource.TestCase{ @@ -30,13 +28,16 @@ func TestAccAPIGatewayMethod_basic(t *testing.T) { CheckDestroy: testAccCheckMethodDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMethodConfig_basic(rInt), - Check: resource.ComposeTestCheckFunc( + Config: testAccMethodConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributes(&conf), - resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), resource.TestCheckResourceAttr(resourceName, "authorization", "NONE"), + resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "1"), resource.TestCheckResourceAttr(resourceName, "request_models.application/json", "Error"), + resource.TestCheckResourceAttr(resourceName, "request_parameters.%", "2"), + resource.TestCheckResourceAttr(resourceName, "request_parameters.method.request.header.Content-Type", "false"), + resource.TestCheckResourceAttr(resourceName, "request_parameters.method.request.querystring.page", "true"), ), }, { @@ -45,12 +46,16 @@ func TestAccAPIGatewayMethod_basic(t *testing.T) { ImportStateIdFunc: testAccMethodImportStateIdFunc(resourceName), ImportStateVerify: true, }, - { - Config: testAccMethodConfig_update(rInt), - Check: resource.ComposeTestCheckFunc( + Config: testAccMethodConfig_update(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributesUpdate(&conf), + resource.TestCheckResourceAttr(resourceName, "authorization", "NONE"), + resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), + resource.TestCheckResourceAttr(resourceName, "request_models.%", "1"), + resource.TestCheckResourceAttr(resourceName, "request_models.application/json", "Error"), + resource.TestCheckResourceAttr(resourceName, "request_parameters.%", "1"), + resource.TestCheckResourceAttr(resourceName, "request_parameters.method.request.querystring.page", "false"), ), }, }, @@ -60,7 +65,7 @@ func TestAccAPIGatewayMethod_basic(t *testing.T) { func TestAccAPIGatewayMethod_customAuthorizer(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Method - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_method.test" resource.ParallelTest(t, resource.TestCase{ @@ -70,14 +75,11 @@ func TestAccAPIGatewayMethod_customAuthorizer(t *testing.T) { CheckDestroy: testAccCheckMethodDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMethodConfig_customAuthorizer(rInt), + Config: testAccMethodConfig_customAuthorizer(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributes(&conf), - resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), resource.TestCheckResourceAttr(resourceName, "authorization", "CUSTOM"), - resource.TestMatchResourceAttr(resourceName, "authorizer_id", regexp.MustCompile("^[a-z0-9]{6}$")), - resource.TestCheckResourceAttr(resourceName, "request_models.application/json", "Error"), + resource.TestCheckResourceAttrSet(resourceName, "authorizer_id"), ), }, { @@ -88,10 +90,9 @@ func TestAccAPIGatewayMethod_customAuthorizer(t *testing.T) { }, { - Config: testAccMethodConfig_update(rInt), + Config: testAccMethodConfig_update(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributesUpdate(&conf), resource.TestCheckResourceAttr(resourceName, "authorization", "NONE"), resource.TestCheckResourceAttr(resourceName, "authorizer_id", ""), ), @@ -103,7 +104,7 @@ func TestAccAPIGatewayMethod_customAuthorizer(t *testing.T) { func TestAccAPIGatewayMethod_cognitoAuthorizer(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Method - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_method.test" resource.ParallelTest(t, resource.TestCase{ @@ -113,26 +114,21 @@ func TestAccAPIGatewayMethod_cognitoAuthorizer(t *testing.T) { CheckDestroy: testAccCheckMethodDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMethodConfig_cognitoAuthorizer(rInt), + Config: testAccMethodConfig_cognitoAuthorizer(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributes(&conf), - resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), resource.TestCheckResourceAttr(resourceName, "authorization", "COGNITO_USER_POOLS"), - resource.TestMatchResourceAttr(resourceName, "authorizer_id", regexp.MustCompile("^[a-z0-9]{6}$")), - resource.TestCheckResourceAttr(resourceName, "request_models.application/json", "Error"), + resource.TestCheckResourceAttrSet(resourceName, "authorizer_id"), resource.TestCheckResourceAttr(resourceName, "authorization_scopes.#", "2"), ), }, { - Config: testAccMethodConfig_cognitoAuthorizerUpdate(rInt), + Config: testAccMethodConfig_cognitoAuthorizerUpdate(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributesUpdate(&conf), resource.TestCheckResourceAttr(resourceName, "authorization", "COGNITO_USER_POOLS"), - resource.TestMatchResourceAttr(resourceName, "authorizer_id", regexp.MustCompile("^[a-z0-9]{6}$")), - resource.TestCheckResourceAttr(resourceName, "request_models.application/json", "Error"), + resource.TestCheckResourceAttrSet(resourceName, "authorizer_id"), resource.TestCheckResourceAttr(resourceName, "authorization_scopes.#", "3"), ), }, @@ -149,7 +145,7 @@ func TestAccAPIGatewayMethod_cognitoAuthorizer(t *testing.T) { func TestAccAPIGatewayMethod_customRequestValidator(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Method - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_method.test" resource.ParallelTest(t, resource.TestCase{ @@ -159,14 +155,10 @@ func TestAccAPIGatewayMethod_customRequestValidator(t *testing.T) { CheckDestroy: testAccCheckMethodDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMethodConfig_customRequestValidator(rInt), + Config: testAccMethodConfig_customRequestValidator(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributes(&conf), - resource.TestCheckResourceAttr(resourceName, "http_method", "GET"), - resource.TestCheckResourceAttr(resourceName, "authorization", "NONE"), - resource.TestCheckResourceAttr(resourceName, "request_models.application/json", "Error"), - resource.TestMatchResourceAttr(resourceName, "request_validator_id", regexp.MustCompile("^[a-z0-9]{6}$")), + resource.TestCheckResourceAttrSet(resourceName, "request_validator_id"), ), }, { @@ -177,10 +169,9 @@ func TestAccAPIGatewayMethod_customRequestValidator(t *testing.T) { }, { - Config: testAccMethodConfig_customRequestValidatorUpdate(rInt), + Config: testAccMethodConfig_customRequestValidatorUpdate(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), - testAccCheckMethodAttributesUpdate(&conf), resource.TestCheckResourceAttr(resourceName, "request_validator_id", ""), ), }, @@ -191,7 +182,7 @@ func TestAccAPIGatewayMethod_customRequestValidator(t *testing.T) { func TestAccAPIGatewayMethod_disappears(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Method - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_method.test" resource.ParallelTest(t, resource.TestCase{ @@ -201,7 +192,7 @@ func TestAccAPIGatewayMethod_disappears(t *testing.T) { CheckDestroy: testAccCheckMethodDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMethodConfig_basic(rInt), + Config: testAccMethodConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfapigateway.ResourceMethod(), resourceName), @@ -215,7 +206,7 @@ func TestAccAPIGatewayMethod_disappears(t *testing.T) { func TestAccAPIGatewayMethod_operationName(t *testing.T) { ctx := acctest.Context(t) var conf apigateway.Method - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_api_gateway_method.test" resource.ParallelTest(t, resource.TestCase{ @@ -225,7 +216,7 @@ func TestAccAPIGatewayMethod_operationName(t *testing.T) { CheckDestroy: testAccCheckMethodDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMethodConfig_operationName(rInt, "getTest"), + Config: testAccMethodConfig_operationName(rName, "getTest"), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "operation_name", "getTest"), @@ -238,7 +229,7 @@ func TestAccAPIGatewayMethod_operationName(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccMethodConfig_operationName(rInt, "describeTest"), + Config: testAccMethodConfig_operationName(rName, "describeTest"), Check: resource.ComposeTestCheckFunc( testAccCheckMethodExists(ctx, resourceName, &conf), resource.TestCheckResourceAttr(resourceName, "operation_name", "describeTest"), @@ -248,55 +239,7 @@ func TestAccAPIGatewayMethod_operationName(t *testing.T) { }) } -func testAccCheckMethodAttributes(conf *apigateway.Method) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *conf.HttpMethod != "GET" { - return fmt.Errorf("Wrong HttpMethod: %q", *conf.HttpMethod) - } - if *conf.AuthorizationType != "NONE" && *conf.AuthorizationType != "CUSTOM" && *conf.AuthorizationType != "COGNITO_USER_POOLS" { - return fmt.Errorf("Wrong Authorization: %q", *conf.AuthorizationType) - } - - if val, ok := conf.RequestParameters["method.request.header.Content-Type"]; !ok { - return fmt.Errorf("missing Content-Type RequestParameters") - } else { - if *val { - return fmt.Errorf("wrong Content-Type RequestParameters value") - } - } - if val, ok := conf.RequestParameters["method.request.querystring.page"]; !ok { - return fmt.Errorf("missing page RequestParameters") - } else { - if !*val { - return fmt.Errorf("wrong query string RequestParameters value") - } - } - - return nil - } -} - -func testAccCheckMethodAttributesUpdate(conf *apigateway.Method) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *conf.HttpMethod != "GET" { - return fmt.Errorf("Wrong HttpMethod: %q", *conf.HttpMethod) - } - if conf.RequestParameters["method.request.header.Content-Type"] != nil { - return fmt.Errorf("Content-Type RequestParameters shouldn't exist") - } - if val, ok := conf.RequestParameters["method.request.querystring.page"]; !ok { - return fmt.Errorf("missing updated page RequestParameters") - } else { - if *val { - return fmt.Errorf("wrong query string RequestParameters updated value") - } - } - - return nil - } -} - -func testAccCheckMethodExists(ctx context.Context, n string, res *apigateway.Method) resource.TestCheckFunc { +func testAccCheckMethodExists(ctx context.Context, n string, v *apigateway.Method) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -309,17 +252,13 @@ func testAccCheckMethodExists(ctx context.Context, n string, res *apigateway.Met conn := acctest.Provider.Meta().(*conns.AWSClient).APIGatewayConn() - req := &apigateway.GetMethodInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - } - describe, err := conn.GetMethodWithContext(ctx, req) + output, err := tfapigateway.FindMethodByThreePartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"]) + if err != nil { return err } - *res = *describe + *v = *output return nil } @@ -334,26 +273,17 @@ func testAccCheckMethodDestroy(ctx context.Context) resource.TestCheckFunc { continue } - req := &apigateway.GetMethodInput{ - HttpMethod: aws.String("GET"), - ResourceId: aws.String(s.RootModule().Resources["aws_api_gateway_resource.test"].Primary.ID), - RestApiId: aws.String(s.RootModule().Resources["aws_api_gateway_rest_api.test"].Primary.ID), - } - _, err := conn.GetMethodWithContext(ctx, req) + _, err := tfapigateway.FindMethodByThreePartKey(ctx, conn, rs.Primary.Attributes["http_method"], rs.Primary.Attributes["resource_id"], rs.Primary.Attributes["rest_api_id"]) - if err == nil { - return fmt.Errorf("API Gateway Method still exists") + if tfresource.NotFound(err) { + continue } - aws2err, ok := err.(awserr.Error) - if !ok { - return err - } - if aws2err.Code() != "NotFoundException" { + if err != nil { return err } - return nil + return fmt.Errorf("API Gateway Method %s still exists", rs.Primary.ID) } return nil @@ -371,14 +301,14 @@ func testAccMethodImportStateIdFunc(resourceName string) resource.ImportStateIdF } } -func testAccMethodConfig_customAuthorizer(rInt int) string { +func testAccMethodConfig_customAuthorizer(rName string) string { return fmt.Sprintf(` resource "aws_api_gateway_rest_api" "test" { - name = "tf-acc-test-custom-auth-%d" + name = %[1]q } resource "aws_iam_role" "invocation_role" { - name = "tf_acc_api_gateway_auth_invocation_role-%d" + name = "%[1]s-invocation" path = "/" assume_role_policy = < 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.MountTargets[0], nil +} + +func statusMountTargetLifeCycleState(ctx context.Context, conn *efs.EFS, id string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindMountTargetByID(ctx, conn, id) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.LifeCycleState), nil + } +} + +func waitMountTargetCreated(ctx context.Context, conn *efs.EFS, id string, timeout time.Duration) (*efs.MountTargetDescription, error) { stateConf := &resource.StateChangeConf{ - Pending: []string{efs.LifeCycleStateAvailable, efs.LifeCycleStateDeleting, efs.LifeCycleStateDeleted}, - Target: []string{}, - Refresh: func() (interface{}, string, error) { - resp, err := conn.DescribeMountTargetsWithContext(ctx, &efs.DescribeMountTargetsInput{ - MountTargetId: aws.String(id), - }) - if err != nil { - if tfawserr.ErrCodeEquals(err, efs.ErrCodeMountTargetNotFound) { - return nil, "", nil - } - - return nil, "error", err - } - - if HasEmptyMountTargets(resp) { - return nil, "", nil - } - - mt := resp.MountTargets[0] - - log.Printf("[DEBUG] Current status of %q: %q", aws.StringValue(mt.MountTargetId), aws.StringValue(mt.LifeCycleState)) - return mt, aws.StringValue(mt.LifeCycleState), nil - }, + Pending: []string{efs.LifeCycleStateCreating}, + Target: []string{efs.LifeCycleStateAvailable}, + Refresh: statusMountTargetLifeCycleState(ctx, conn, id), Timeout: timeout, Delay: 2 * time.Second, MinTimeout: 3 * time.Second, } - _, err := stateConf.WaitForStateContext(ctx) - return err + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*efs.MountTargetDescription); ok { + return output, err + } + + return nil, err } -func HasEmptyMountTargets(mto *efs.DescribeMountTargetsOutput) bool { - if mto != nil && len(mto.MountTargets) > 0 { - return false +func waitMountTargetDeleted(ctx context.Context, conn *efs.EFS, id string, timeout time.Duration) (*efs.MountTargetDescription, error) { + stateConf := &resource.StateChangeConf{ + Pending: []string{efs.LifeCycleStateAvailable, efs.LifeCycleStateDeleting, efs.LifeCycleStateDeleted}, + Target: []string{}, + Refresh: statusMountTargetLifeCycleState(ctx, conn, id), + Timeout: timeout, + Delay: 2 * time.Second, + MinTimeout: 3 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*efs.MountTargetDescription); ok { + return output, err } - return true + + return nil, err } diff --git a/internal/service/efs/mount_target_test.go b/internal/service/efs/mount_target_test.go index f7e88c5d429..1df1660234c 100644 --- a/internal/service/efs/mount_target_test.go +++ b/internal/service/efs/mount_target_test.go @@ -6,23 +6,20 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/ec2" "github.com/aws/aws-sdk-go/service/efs" - "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" tfefs "github.com/hashicorp/terraform-provider-aws/internal/service/efs" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccEFSMountTarget_basic(t *testing.T) { ctx := acctest.Context(t) var mount efs.MountTargetDescription - ct := fmt.Sprintf("createtoken-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_efs_mount_target.test" resourceName2 := "aws_efs_mount_target.test2" @@ -33,9 +30,9 @@ func TestAccEFSMountTarget_basic(t *testing.T) { CheckDestroy: testAccCheckMountTargetDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMountTargetConfig_basic(ct), + Config: testAccMountTargetConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckMountTarget(ctx, resourceName, &mount), + testAccCheckMountTargetExists(ctx, resourceName, &mount), resource.TestCheckResourceAttrSet(resourceName, "availability_zone_id"), resource.TestCheckResourceAttrSet(resourceName, "availability_zone_name"), acctest.MatchResourceAttrRegionalHostname(resourceName, "dns_name", "efs", regexp.MustCompile(`fs-[^.]+`)), @@ -52,10 +49,10 @@ func TestAccEFSMountTarget_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccMountTargetConfig_modified(ct), + Config: testAccMountTargetConfig_modified(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckMountTarget(ctx, resourceName, &mount), - testAccCheckMountTarget(ctx, resourceName2, &mount), + testAccCheckMountTargetExists(ctx, resourceName, &mount), + testAccCheckMountTargetExists(ctx, resourceName2, &mount), acctest.MatchResourceAttrRegionalHostname(resourceName, "dns_name", "efs", regexp.MustCompile(`fs-[^.]+`)), acctest.MatchResourceAttrRegionalHostname(resourceName2, "dns_name", "efs", regexp.MustCompile(`fs-[^.]+`)), ), @@ -67,19 +64,19 @@ func TestAccEFSMountTarget_basic(t *testing.T) { func TestAccEFSMountTarget_disappears(t *testing.T) { ctx := acctest.Context(t) var mount efs.MountTargetDescription + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_efs_mount_target.test" - ct := fmt.Sprintf("createtoken-%d", sdkacctest.RandInt()) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, efs.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckVPNGatewayDestroy(ctx), + CheckDestroy: testAccCheckMountTargetDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccMountTargetConfig_basic(ct), + Config: testAccMountTargetConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckMountTarget(ctx, resourceName, &mount), + testAccCheckMountTargetExists(ctx, resourceName, &mount), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfefs.ResourceMountTarget(), resourceName), ), ExpectNonEmptyPlan: true, @@ -91,8 +88,8 @@ func TestAccEFSMountTarget_disappears(t *testing.T) { func TestAccEFSMountTarget_ipAddress(t *testing.T) { ctx := acctest.Context(t) var mount efs.MountTargetDescription - resourceName := "aws_efs_mount_target.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_efs_mount_target.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -103,7 +100,7 @@ func TestAccEFSMountTarget_ipAddress(t *testing.T) { { Config: testAccMountTargetConfig_ipAddress(rName, "10.0.0.100"), Check: resource.ComposeTestCheckFunc( - testAccCheckMountTarget(ctx, resourceName, &mount), + testAccCheckMountTargetExists(ctx, resourceName, &mount), resource.TestCheckResourceAttr(resourceName, "ip_address", "10.0.0.100"), ), }, @@ -120,8 +117,8 @@ func TestAccEFSMountTarget_ipAddress(t *testing.T) { func TestAccEFSMountTarget_IPAddress_emptyString(t *testing.T) { ctx := acctest.Context(t) var mount efs.MountTargetDescription - resourceName := "aws_efs_mount_target.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_efs_mount_target.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -132,7 +129,7 @@ func TestAccEFSMountTarget_IPAddress_emptyString(t *testing.T) { { Config: testAccMountTargetConfig_ipAddressNullIP(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckMountTarget(ctx, resourceName, &mount), + testAccCheckMountTargetExists(ctx, resourceName, &mount), resource.TestMatchResourceAttr(resourceName, "ip_address", regexp.MustCompile(`\d+\.\d+\.\d+\.\d+`)), ), }, @@ -145,27 +142,6 @@ func TestAccEFSMountTarget_IPAddress_emptyString(t *testing.T) { }) } -func TestMountTarget_hasEmptyMountTargets(t *testing.T) { - t.Parallel() - - mto := &efs.DescribeMountTargetsOutput{ - MountTargets: []*efs.MountTargetDescription{}, - } - - actual := tfefs.HasEmptyMountTargets(mto) - if !actual { - t.Fatalf("Expected return value to be true, got %t", actual) - } - - // Add an empty mount target. - mto.MountTargets = append(mto.MountTargets, &efs.MountTargetDescription{}) - - actual = tfefs.HasEmptyMountTargets(mto) - if actual { - t.Fatalf("Expected return value to be false, got %t", actual) - } -} - func testAccCheckMountTargetDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).EFSConn() @@ -174,291 +150,97 @@ func testAccCheckMountTargetDestroy(ctx context.Context) resource.TestCheckFunc continue } - resp, err := conn.DescribeMountTargetsWithContext(ctx, &efs.DescribeMountTargetsInput{ - MountTargetId: aws.String(rs.Primary.ID), - }) - if err != nil { - if tfawserr.ErrCodeEquals(err, efs.ErrCodeMountTargetNotFound) { - // gone - return nil - } - return fmt.Errorf("Error describing EFS Mount in tests: %s", err) + _, err := tfefs.FindMountTargetByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue } - if len(resp.MountTargets) > 0 { - return fmt.Errorf("EFS Mount target %q still exists", rs.Primary.ID) + + if err != nil { + return err } + + return fmt.Errorf("EFS Mount Target %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckMountTarget(ctx context.Context, resourceID string, mount *efs.MountTargetDescription) resource.TestCheckFunc { +func testAccCheckMountTargetExists(ctx context.Context, n string, v *efs.MountTargetDescription) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceID] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", resourceID) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - fs, ok := s.RootModule().Resources[resourceID] - if !ok { - return fmt.Errorf("Not found: %s", resourceID) + return fmt.Errorf("No EFS Mount Target ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).EFSConn() - mt, err := conn.DescribeMountTargetsWithContext(ctx, &efs.DescribeMountTargetsInput{ - MountTargetId: aws.String(fs.Primary.ID), - }) + + output, err := tfefs.FindMountTargetByID(ctx, conn, rs.Primary.ID) + if err != nil { return err } - if aws.StringValue(mt.MountTargets[0].MountTargetId) != fs.Primary.ID { - return fmt.Errorf("Mount target ID mismatch: %q != %q", - *mt.MountTargets[0].MountTargetId, fs.Primary.ID) - } - - *mount = *mt.MountTargets[0] + *v = *output return nil } } -func testAccMountTargetConfig_basic(ct string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - +func testAccMountTargetConfig_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` resource "aws_efs_file_system" "test" { - creation_token = "%s" - tags = { - Name = "tf-acc-efs-mount-target-test" + Name = %[1]q } } +`, rName)) +} +func testAccMountTargetConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccMountTargetConfig_base(rName), ` resource "aws_efs_mount_target" "test" { file_system_id = aws_efs_file_system.test.id - subnet_id = aws_subnet.test.id -} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = "tf-acc-efs-mount-target-test" - } -} - -resource "aws_subnet" "test" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.1.0/24" - - tags = { - Name = "tf-acc-efs-mount-target-test" - } -} -`, ct) -} - -func testAccMountTargetConfig_modified(ct string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } + subnet_id = aws_subnet.test[0].id } - -resource "aws_efs_file_system" "test" { - creation_token = "%s" - - tags = { - Name = "tf-acc-efs-mount-target-test" - } +`) } +func testAccMountTargetConfig_modified(rName string) string { + return acctest.ConfigCompose(testAccMountTargetConfig_base(rName), ` resource "aws_efs_mount_target" "test" { file_system_id = aws_efs_file_system.test.id - subnet_id = aws_subnet.test.id + subnet_id = aws_subnet.test[0].id } resource "aws_efs_mount_target" "test2" { file_system_id = aws_efs_file_system.test.id - subnet_id = aws_subnet.test2.id -} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = "tf-acc-efs-mount-target-test" - } -} - -resource "aws_subnet" "test" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.1.0/24" - - tags = { - Name = "tf-acc-efs-mount-target-test" - } -} - -resource "aws_subnet" "test2" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[1] - cidr_block = "10.0.2.0/24" - - tags = { - Name = "tf-acc-efs-mount-target-test2" - } + subnet_id = aws_subnet.test[1].id } -`, ct) +`) } func testAccMountTargetConfig_ipAddress(rName, ipAddress string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.0.0/24" - - tags = { - Name = %[1]q - } -} - -resource "aws_efs_file_system" "test" { - tags = { - Name = %[1]q - } -} - + return acctest.ConfigCompose(testAccMountTargetConfig_base(rName), fmt.Sprintf(` resource "aws_efs_mount_target" "test" { file_system_id = aws_efs_file_system.test.id - ip_address = %[2]q - subnet_id = aws_subnet.test.id + ip_address = %[1]q + subnet_id = aws_subnet.test[0].id } -`, rName, ipAddress) +`, ipAddress)) } func testAccMountTargetConfig_ipAddressNullIP(rName string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test" { - vpc_id = aws_vpc.test.id - availability_zone = data.aws_availability_zones.available.names[0] - cidr_block = "10.0.0.0/24" - - tags = { - Name = %[1]q - } -} - -resource "aws_efs_file_system" "test" { - tags = { - Name = %[1]q - } -} - + return acctest.ConfigCompose(testAccMountTargetConfig_base(rName), ` resource "aws_efs_mount_target" "test" { file_system_id = aws_efs_file_system.test.id ip_address = null - subnet_id = aws_subnet.test.id -} -`, rName) + subnet_id = aws_subnet.test[0].id } - -func testAccCheckVPNGatewayDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EC2Conn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_vpn_gateway" { - continue - } - - // Try to find the resource - resp, err := conn.DescribeVpnGatewaysWithContext(ctx, &ec2.DescribeVpnGatewaysInput{ - VpnGatewayIds: []*string{aws.String(rs.Primary.ID)}, - }) - if err == nil { - var v *ec2.VpnGateway - for _, g := range resp.VpnGateways { - if *g.VpnGatewayId == rs.Primary.ID { - v = g - } - } - - if v == nil { - // wasn't found - return nil - } - - if *v.State != "deleted" { - return fmt.Errorf("Expected VPN Gateway to be in deleted state, but was not: %s", v) - } - return nil - } - - // Verify the error is what we want - ec2err, ok := err.(awserr.Error) - if !ok { - return err - } - if ec2err.Code() != "InvalidVpnGatewayID.NotFound" { - return err - } - } - - return nil - } +`) } diff --git a/internal/service/elasticache/parameter_group.go b/internal/service/elasticache/parameter_group.go index 522ceca9b65..91f24fd3fa6 100644 --- a/internal/service/elasticache/parameter_group.go +++ b/internal/service/elasticache/parameter_group.go @@ -9,7 +9,6 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elasticache" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -330,11 +329,10 @@ func deleteParameterGroup(ctx context.Context, conn *elasticache.ElastiCache, na err := resource.RetryContext(ctx, 3*time.Minute, func() *resource.RetryError { _, err := conn.DeleteCacheParameterGroupWithContext(ctx, &deleteOpts) if err != nil { - awsErr, ok := err.(awserr.Error) - if ok && awsErr.Code() == "CacheParameterGroupNotFoundFault" { + if tfawserr.ErrCodeEquals(err, elasticache.ErrCodeCacheParameterGroupNotFoundFault) { return nil } - if ok && awsErr.Code() == "InvalidCacheParameterGroupState" { + if tfawserr.ErrCodeEquals(err, elasticache.ErrCodeInvalidCacheParameterGroupStateFault) { return resource.RetryableError(err) } return resource.NonRetryableError(err) diff --git a/internal/service/elasticache/security_group.go b/internal/service/elasticache/security_group.go index e07865384d1..117dd69b89f 100644 --- a/internal/service/elasticache/security_group.go +++ b/internal/service/elasticache/security_group.go @@ -6,8 +6,8 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elasticache" + "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" @@ -102,22 +102,15 @@ func resourceSecurityGroupDelete(ctx context.Context, d *schema.ResourceData, me _, err := conn.DeleteCacheSecurityGroupWithContext(ctx, &elasticache.DeleteCacheSecurityGroupInput{ CacheSecurityGroupName: aws.String(d.Id()), }) + + if tfawserr.ErrCodeEquals(err, "InvalidCacheSecurityGroupState", "DependencyViolation") { + return resource.RetryableError(err) + } + if err != nil { - apierr, ok := err.(awserr.Error) - if !ok { - return resource.RetryableError(err) - } - log.Printf("[DEBUG] APIError.Code: %v", apierr.Code()) - switch apierr.Code() { - case "InvalidCacheSecurityGroupState": - return resource.RetryableError(err) - case "DependencyViolation": - // If it is a dependency violation, we want to retry - return resource.RetryableError(err) - default: - return resource.NonRetryableError(err) - } + return resource.RetryableError(err) } + return nil }) diff --git a/internal/service/elasticache/subnet_group.go b/internal/service/elasticache/subnet_group.go index 158d7f740c0..c04626d8dcf 100644 --- a/internal/service/elasticache/subnet_group.go +++ b/internal/service/elasticache/subnet_group.go @@ -7,11 +7,9 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elasticache" "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/errs/sdkdiag" @@ -27,6 +25,7 @@ func ResourceSubnetGroup() *schema.Resource { ReadWithoutTimeout: resourceSubnetGroupRead, UpdateWithoutTimeout: resourceSubnetGroupUpdate, DeleteWithoutTimeout: resourceSubnetGroupDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -56,7 +55,6 @@ func ResourceSubnetGroup() *schema.Resource { Type: schema.TypeSet, Required: true, Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, }, "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), @@ -84,32 +82,24 @@ func resourceSubnetGroupCreate(ctx context.Context, d *schema.ResourceData, meta defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - // Get the group properties name := d.Get("name").(string) - desc := d.Get("description").(string) - subnetIdsSet := d.Get("subnet_ids").(*schema.Set) - - log.Printf("[DEBUG] Cache subnet group create: name: %s, description: %s", name, desc) - - subnetIds := flex.ExpandStringSet(subnetIdsSet) - - req := &elasticache.CreateCacheSubnetGroupInput{ - CacheSubnetGroupDescription: aws.String(desc), + input := &elasticache.CreateCacheSubnetGroupInput{ + CacheSubnetGroupDescription: aws.String(d.Get("description").(string)), CacheSubnetGroupName: aws.String(name), - SubnetIds: subnetIds, + SubnetIds: flex.ExpandStringSet(d.Get("subnet_ids").(*schema.Set)), } if len(tags) > 0 { - req.Tags = Tags(tags.IgnoreAWS()) + input.Tags = Tags(tags.IgnoreAWS()) } - output, err := conn.CreateCacheSubnetGroupWithContext(ctx, req) + output, err := conn.CreateCacheSubnetGroupWithContext(ctx, input) - if req.Tags != nil && verify.ErrorISOUnsupported(conn.PartitionID, err) { + if input.Tags != nil && verify.ErrorISOUnsupported(conn.PartitionID, err) { log.Printf("[WARN] failed creating ElastiCache Subnet Group with tags: %s. Trying create without tags.", err) - req.Tags = nil - output, err = conn.CreateCacheSubnetGroupWithContext(ctx, req) + input.Tags = nil + output, err = conn.CreateCacheSubnetGroupWithContext(ctx, input) } if err != nil { @@ -123,7 +113,7 @@ func resourceSubnetGroupCreate(ctx context.Context, d *schema.ResourceData, meta d.SetId(strings.ToLower(name)) // In some partitions, only post-create tagging supported - if req.Tags == nil && len(tags) > 0 { + if input.Tags == nil && len(tags) > 0 { err := UpdateTags(ctx, conn, aws.StringValue(output.CacheSubnetGroup.ARN), nil, tags) if err != nil { @@ -157,15 +147,15 @@ func resourceSubnetGroupRead(ctx context.Context, d *schema.ResourceData, meta i return sdkdiag.AppendErrorf(diags, "reading ElastiCache Subnet Group (%s): %s", d.Id(), err) } - var subnetIds []*string + var subnetIDs []*string for _, subnet := range group.Subnets { - subnetIds = append(subnetIds, subnet.SubnetIdentifier) + subnetIDs = append(subnetIDs, subnet.SubnetIdentifier) } d.Set("arn", group.ARN) d.Set("name", group.CacheSubnetGroupName) d.Set("description", group.CacheSubnetGroupDescription) - d.Set("subnet_ids", subnetIds) + d.Set("subnet_ids", aws.StringValueSlice(subnetIDs)) tags, err := ListTags(ctx, conn, d.Get("arn").(string)) @@ -199,19 +189,13 @@ func resourceSubnetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta conn := meta.(*conns.AWSClient).ElastiCacheConn() if d.HasChanges("subnet_ids", "description") { - var subnets []*string - if v := d.Get("subnet_ids"); v != nil { - for _, v := range v.(*schema.Set).List() { - subnets = append(subnets, aws.String(v.(string))) - } + input := &elasticache.ModifyCacheSubnetGroupInput{ + CacheSubnetGroupDescription: aws.String(d.Get("description").(string)), + CacheSubnetGroupName: aws.String(d.Get("name").(string)), + SubnetIds: flex.ExpandStringSet(d.Get("subnet_ids").(*schema.Set)), } - log.Printf("[DEBUG] Updating ElastiCache Subnet Group") - _, err := conn.ModifyCacheSubnetGroupWithContext(ctx, &elasticache.ModifyCacheSubnetGroupInput{ - CacheSubnetGroupName: aws.String(d.Get("name").(string)), - CacheSubnetGroupDescription: aws.String(d.Get("description").(string)), - SubnetIds: subnets, - }) + _, err := conn.ModifyCacheSubnetGroupWithContext(ctx, input) if err != nil { return sdkdiag.AppendErrorf(diags, "updating ElastiCache Subnet Group (%s): %s", d.Id(), err) @@ -222,6 +206,7 @@ func resourceSubnetGroupUpdate(ctx context.Context, d *schema.ResourceData, meta o, n := d.GetChange("tags_all") err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n) + if err != nil { if v, ok := d.GetOk("tags"); (ok && len(v.(map[string]interface{})) > 0) || !verify.ErrorISOUnsupported(conn.PartitionID, err) { // explicitly setting tags or not an iso-unsupported error @@ -239,33 +224,12 @@ func resourceSubnetGroupDelete(ctx context.Context, d *schema.ResourceData, meta var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ElastiCacheConn() - log.Printf("[DEBUG] Cache subnet group delete: %s", d.Id()) - - err := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { - _, err := conn.DeleteCacheSubnetGroupWithContext(ctx, &elasticache.DeleteCacheSubnetGroupInput{ - CacheSubnetGroupName: aws.String(d.Id()), - }) - if err != nil { - apierr, ok := err.(awserr.Error) - if !ok { - return resource.RetryableError(err) - } - log.Printf("[DEBUG] APIError.Code: %v", apierr.Code()) - switch apierr.Code() { - case "DependencyViolation": - // If it is a dependency violation, we want to retry - return resource.RetryableError(err) - default: - return resource.NonRetryableError(err) - } - } - return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.DeleteCacheSubnetGroupWithContext(ctx, &elasticache.DeleteCacheSubnetGroupInput{ + log.Printf("[DEBUG] Deleting ElastiCache Subnet Group: %s", d.Id()) + _, err := tfresource.RetryWhenAWSErrCodeEquals(ctx, 5*time.Minute, func() (interface{}, error) { + return conn.DeleteCacheSubnetGroupWithContext(ctx, &elasticache.DeleteCacheSubnetGroupInput{ CacheSubnetGroupName: aws.String(d.Id()), }) - } + }, "DependencyViolation") if tfawserr.ErrCodeEquals(err, elasticache.ErrCodeCacheSubnetGroupNotFoundFault) { return diags diff --git a/internal/service/elasticbeanstalk/application_version_test.go b/internal/service/elasticbeanstalk/application_version_test.go index eac66a21e8f..3f6f431689b 100644 --- a/internal/service/elasticbeanstalk/application_version_test.go +++ b/internal/service/elasticbeanstalk/application_version_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elasticbeanstalk" + "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" @@ -131,11 +131,7 @@ func testAccCheckApplicationVersionDestroy(ctx context.Context) resource.TestChe return nil } - ec2err, ok := err.(awserr.Error) - if !ok { - return err - } - if ec2err.Code() != "InvalidParameterValue" { + if !tfawserr.ErrCodeEquals(err, "InvalidParameterValue") { return err } } diff --git a/internal/service/elasticbeanstalk/configuration_template.go b/internal/service/elasticbeanstalk/configuration_template.go index ac49361221c..5480e93cfe6 100644 --- a/internal/service/elasticbeanstalk/configuration_template.go +++ b/internal/service/elasticbeanstalk/configuration_template.go @@ -3,15 +3,16 @@ package elasticbeanstalk import ( "context" "log" - "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elasticbeanstalk" + "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceConfigurationTemplate() *schema.Resource { @@ -22,11 +23,6 @@ func ResourceConfigurationTemplate() *schema.Resource { DeleteWithoutTimeout: resourceConfigurationTemplateDelete, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, "application": { Type: schema.TypeString, Required: true, @@ -41,6 +37,11 @@ func ResourceConfigurationTemplate() *schema.Resource { Optional: true, ForceNew: true, }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, "setting": { Type: schema.TypeSet, Optional: true, @@ -99,33 +100,22 @@ func resourceConfigurationTemplateRead(ctx context.Context, d *schema.ResourceDa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ElasticBeanstalkConn() - log.Printf("[DEBUG] Elastic Beanstalk configuration template read: %s", d.Get("name").(string)) + settings, err := FindConfigurationSettingsByTwoPartKey(ctx, conn, d.Get("application").(string), d.Id()) - resp, err := conn.DescribeConfigurationSettingsWithContext(ctx, &elasticbeanstalk.DescribeConfigurationSettingsInput{ - TemplateName: aws.String(d.Id()), - ApplicationName: aws.String(d.Get("application").(string)), - }) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Elastic Beanstalk Configuration Template (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } if err != nil { - if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() == "InvalidParameterValue" && strings.Contains(awsErr.Message(), "No Configuration Template named") { - log.Printf("[WARN] No Configuration Template named (%s) found", d.Id()) - d.SetId("") - return diags - } else if awsErr.Code() == "InvalidParameterValue" && strings.Contains(awsErr.Message(), "No Platform named") { - log.Printf("[WARN] No Platform named (%s) found", d.Get("solution_stack_name").(string)) - d.SetId("") - return diags - } - } return sdkdiag.AppendErrorf(diags, "reading Elastic Beanstalk Configuration Template (%s): %s", d.Id(), err) } - if len(resp.ConfigurationSettings) != 1 { - return sdkdiag.AppendErrorf(diags, "reading application properties: found %d applications, expected 1", len(resp.ConfigurationSettings)) - } - - d.Set("description", resp.ConfigurationSettings[0].Description) + d.Set("application", settings.ApplicationName) + d.Set("description", settings.Description) + d.Set("name", settings.TemplateName) + d.Set("solution_stack_name", settings.SolutionStackName) return diags } @@ -243,6 +233,36 @@ func resourceConfigurationTemplateDelete(ctx context.Context, d *schema.Resource return diags } +func FindConfigurationSettingsByTwoPartKey(ctx context.Context, conn *elasticbeanstalk.ElasticBeanstalk, applicationName, templateName string) (*elasticbeanstalk.ConfigurationSettingsDescription, error) { + input := &elasticbeanstalk.DescribeConfigurationSettingsInput{ + ApplicationName: aws.String(applicationName), + TemplateName: aws.String(templateName), + } + + output, err := conn.DescribeConfigurationSettingsWithContext(ctx, input) + + if tfawserr.ErrMessageContains(err, "InvalidParameterValue", "No Configuration Template named") || tfawserr.ErrMessageContains(err, "InvalidParameterValue", "No Application named") { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.ConfigurationSettings) == 0 || output.ConfigurationSettings[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.ConfigurationSettings); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.ConfigurationSettings[0], nil +} + func gatherOptionSettings(d *schema.ResourceData) []*elasticbeanstalk.ConfigurationOptionSetting { optionSettingsSet, ok := d.Get("setting").(*schema.Set) if !ok || optionSettingsSet == nil { diff --git a/internal/service/elasticbeanstalk/configuration_template_test.go b/internal/service/elasticbeanstalk/configuration_template_test.go index ff9e30142fd..b4e7c06256e 100644 --- a/internal/service/elasticbeanstalk/configuration_template_test.go +++ b/internal/service/elasticbeanstalk/configuration_template_test.go @@ -5,19 +5,21 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elasticbeanstalk" 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" + tfelasticbeanstalk "github.com/hashicorp/terraform-provider-aws/internal/service/elasticbeanstalk" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func TestAccElasticBeanstalkConfigurationTemplate_Beanstalk_basic(t *testing.T) { +func TestAccElasticBeanstalkConfigurationTemplate_basic(t *testing.T) { ctx := acctest.Context(t) var config elasticbeanstalk.ConfigurationSettingsDescription + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_elastic_beanstalk_configuration_template.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -26,18 +28,20 @@ func TestAccElasticBeanstalkConfigurationTemplate_Beanstalk_basic(t *testing.T) CheckDestroy: testAccCheckConfigurationTemplateDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigurationTemplateConfig_basic(sdkacctest.RandString(5)), + Config: testAccConfigurationTemplateConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckConfigurationTemplateExists(ctx, "aws_elastic_beanstalk_configuration_template.tf_template", &config), + testAccCheckConfigurationTemplateExists(ctx, resourceName, &config), ), }, }, }) } -func TestAccElasticBeanstalkConfigurationTemplate_Beanstalk_vpc(t *testing.T) { +func TestAccElasticBeanstalkConfigurationTemplate_disappears(t *testing.T) { ctx := acctest.Context(t) var config elasticbeanstalk.ConfigurationSettingsDescription + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_elastic_beanstalk_configuration_template.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -46,18 +50,22 @@ func TestAccElasticBeanstalkConfigurationTemplate_Beanstalk_vpc(t *testing.T) { CheckDestroy: testAccCheckConfigurationTemplateDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigurationTemplateConfig_vpc(sdkacctest.RandString(5)), + Config: testAccConfigurationTemplateConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckConfigurationTemplateExists(ctx, "aws_elastic_beanstalk_configuration_template.tf_template", &config), + testAccCheckConfigurationTemplateExists(ctx, resourceName, &config), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelasticbeanstalk.ResourceConfigurationTemplate(), resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) } -func TestAccElasticBeanstalkConfigurationTemplate_Beanstalk_setting(t *testing.T) { +func TestAccElasticBeanstalkConfigurationTemplate_vpc(t *testing.T) { ctx := acctest.Context(t) var config elasticbeanstalk.ConfigurationSettingsDescription + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_elastic_beanstalk_configuration_template.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -66,12 +74,33 @@ func TestAccElasticBeanstalkConfigurationTemplate_Beanstalk_setting(t *testing.T CheckDestroy: testAccCheckConfigurationTemplateDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigurationTemplateConfig_setting(sdkacctest.RandString(5)), + Config: testAccConfigurationTemplateConfig_vpc(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckConfigurationTemplateExists(ctx, "aws_elastic_beanstalk_configuration_template.tf_template", &config), - resource.TestCheckResourceAttr( - "aws_elastic_beanstalk_configuration_template.tf_template", "setting.#", "1"), - resource.TestCheckTypeSetElemNestedAttrs("aws_elastic_beanstalk_configuration_template.tf_template", "setting.*", map[string]string{ + testAccCheckConfigurationTemplateExists(ctx, resourceName, &config), + ), + }, + }, + }) +} + +func TestAccElasticBeanstalkConfigurationTemplate_settings(t *testing.T) { + ctx := acctest.Context(t) + var config elasticbeanstalk.ConfigurationSettingsDescription + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_elastic_beanstalk_configuration_template.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elasticbeanstalk.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckConfigurationTemplateDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccConfigurationTemplateConfig_setting(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationTemplateExists(ctx, resourceName, &config), + resource.TestCheckResourceAttr(resourceName, "setting.#", "1"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "setting.*", map[string]string{ "value": "m1.small", }), ), @@ -89,141 +118,101 @@ func testAccCheckConfigurationTemplateDestroy(ctx context.Context) resource.Test continue } - // Try to find the Configuration Template - opts := elasticbeanstalk.DescribeConfigurationSettingsInput{ - TemplateName: aws.String(rs.Primary.ID), - ApplicationName: aws.String(rs.Primary.Attributes["application"]), - } - resp, err := conn.DescribeConfigurationSettingsWithContext(ctx, &opts) - if err == nil { - if len(resp.ConfigurationSettings) > 0 { - return fmt.Errorf("Elastic Beanstalk Application still exists.") - } + _, err := tfelasticbeanstalk.FindConfigurationSettingsByTwoPartKey(ctx, conn, rs.Primary.Attributes["application"], rs.Primary.ID) - return nil + if tfresource.NotFound(err) { + continue } - // Verify the error is what we want - ec2err, ok := err.(awserr.Error) - if !ok { + if err != nil { return err } - switch { - case ec2err.Code() == "InvalidBeanstalkConfigurationTemplateID.NotFound": - return nil - // This error can be returned when the beanstalk application no longer exists. - case ec2err.Code() == "InvalidParameterValue": - return nil - default: - return err - } + return fmt.Errorf("Elastic Beanstalk Configuration Template %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckConfigurationTemplateExists(ctx context.Context, n string, config *elasticbeanstalk.ConfigurationSettingsDescription) resource.TestCheckFunc { +func testAccCheckConfigurationTemplateExists(ctx context.Context, n string, v *elasticbeanstalk.ConfigurationSettingsDescription) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ElasticBeanstalkConn() rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("Elastic Beanstalk config ID is not set") + return fmt.Errorf("No Elastic Beanstalk Configuration Template ID is set") } - opts := elasticbeanstalk.DescribeConfigurationSettingsInput{ - TemplateName: aws.String(rs.Primary.ID), - ApplicationName: aws.String(rs.Primary.Attributes["application"]), - } - resp, err := conn.DescribeConfigurationSettingsWithContext(ctx, &opts) + conn := acctest.Provider.Meta().(*conns.AWSClient).ElasticBeanstalkConn() + + output, err := tfelasticbeanstalk.FindConfigurationSettingsByTwoPartKey(ctx, conn, rs.Primary.Attributes["application"], rs.Primary.ID) + if err != nil { return err } - if len(resp.ConfigurationSettings) == 0 { - return fmt.Errorf("Elastic Beanstalk Configurations not found.") - } - *config = *resp.ConfigurationSettings[0] + *v = *output return nil } } -func testAccConfigurationTemplateConfig_basic(r string) string { +func testAccConfigurationTemplateConfig_basic(rName string) string { return fmt.Sprintf(` -resource "aws_elastic_beanstalk_application" "tftest" { - name = "tf-test-%s" - description = "tf-test-desc-%s" +resource "aws_elastic_beanstalk_application" "test" { + name = %[1]q + description = "testing" } -resource "aws_elastic_beanstalk_configuration_template" "tf_template" { - name = "tf-test-template-config" - application = aws_elastic_beanstalk_application.tftest.name +resource "aws_elastic_beanstalk_configuration_template" "test" { + name = %[1]q + application = aws_elastic_beanstalk_application.test.name solution_stack_name = "64bit Amazon Linux running Python" } -`, r, r) -} - -func testAccConfigurationTemplateConfig_vpc(name string) string { - return fmt.Sprintf(` -resource "aws_vpc" "tf_b_test" { - cidr_block = "10.0.0.0/16" - - tags = { - Name = "terraform-testacc-elastic-beanstalk-cfg-tpl-vpc" - } -} - -resource "aws_subnet" "main" { - vpc_id = aws_vpc.tf_b_test.id - cidr_block = "10.0.0.0/24" - - tags = { - Name = "tf-acc-elastic-beanstalk-cfg-tpl-vpc" - } +`, rName) } -resource "aws_elastic_beanstalk_application" "tftest" { - name = "tf-test-%s" - description = "tf-test-desc" +func testAccConfigurationTemplateConfig_vpc(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 1), fmt.Sprintf(` +resource "aws_elastic_beanstalk_application" "test" { + name = %[1]q + description = "testing" } -resource "aws_elastic_beanstalk_configuration_template" "tf_template" { - name = "tf-test-%s" - application = aws_elastic_beanstalk_application.tftest.name +resource "aws_elastic_beanstalk_configuration_template" "test" { + name = %[1]q + application = aws_elastic_beanstalk_application.test.name solution_stack_name = "64bit Amazon Linux running Python" setting { namespace = "aws:ec2:vpc" name = "VPCId" - value = aws_vpc.tf_b_test.id + value = aws_vpc.test.id } setting { namespace = "aws:ec2:vpc" name = "Subnets" - value = aws_subnet.main.id + value = aws_subnet.test[0].id } } -`, name, name) +`, rName)) } -func testAccConfigurationTemplateConfig_setting(name string) string { +func testAccConfigurationTemplateConfig_setting(rName string) string { return fmt.Sprintf(` -resource "aws_elastic_beanstalk_application" "tftest" { - name = "tf-test-%s" - description = "tf-test-desc" +resource "aws_elastic_beanstalk_application" "test" { + name = %[1]q + description = "testing" } -resource "aws_elastic_beanstalk_configuration_template" "tf_template" { - name = "tf-test-%s" - application = aws_elastic_beanstalk_application.tftest.name +resource "aws_elastic_beanstalk_configuration_template" "test" { + name = %[1]q + application = aws_elastic_beanstalk_application.test.name solution_stack_name = "64bit Amazon Linux running Python" @@ -233,5 +222,5 @@ resource "aws_elastic_beanstalk_configuration_template" "tf_template" { value = "m1.small" } } -`, name, name) +`, rName) } diff --git a/internal/service/elasticsearch/domain_test.go b/internal/service/elasticsearch/domain_test.go index 9a3bf866012..129a1eb33fd 100644 --- a/internal/service/elasticsearch/domain_test.go +++ b/internal/service/elasticsearch/domain_test.go @@ -8,10 +8,8 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" elasticsearch "github.com/aws/aws-sdk-go/service/elasticsearchservice" - "github.com/aws/aws-sdk-go/service/elb" 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" @@ -1297,7 +1295,7 @@ func TestAccElasticsearchDomain_tags(t *testing.T) { PreCheck: func() { acctest.PreCheck(t); testAccPreCheckIAMServiceLinkedRole(t) }, ErrorCheck: acctest.ErrorCheck(t, elasticsearch.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckELBDestroy(ctx), + CheckDestroy: testAccCheckDomainDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccDomainConfig_tags1(rName, "key1", "value1"), @@ -3044,38 +3042,3 @@ func testAccPreCheckCognitoIdentityProvider(ctx context.Context, t *testing.T) { t.Fatalf("unexpected PreCheck error: %s", err) } } - -func testAccCheckELBDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_elb" { - continue - } - - describe, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, - }) - - if err == nil { - if len(describe.LoadBalancerDescriptions) != 0 && - *describe.LoadBalancerDescriptions[0].LoadBalancerName == rs.Primary.ID { - return fmt.Errorf("ELB still exists") - } - } - - // Verify the error - providerErr, ok := err.(awserr.Error) - if !ok { - return err - } - - if providerErr.Code() != elb.ErrCodeAccessPointNotFoundException { - return fmt.Errorf("Unexpected error: %s", err) - } - } - - return nil - } -} diff --git a/internal/service/elb/app_cookie_stickiness_policy.go b/internal/service/elb/app_cookie_stickiness_policy.go index 05c9d836d7c..883df48b405 100644 --- a/internal/service/elb/app_cookie_stickiness_policy.go +++ b/internal/service/elb/app_cookie_stickiness_policy.go @@ -2,7 +2,6 @@ package elb import ( "context" - "errors" "fmt" "log" "regexp" @@ -13,53 +12,51 @@ import ( "github.com/aws/aws-sdk-go/service/elb" "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceAppCookieStickinessPolicy() *schema.Resource { return &schema.Resource{ - // There is no concept of "updating" an App Stickiness policy in - // the AWS API. CreateWithoutTimeout: resourceAppCookieStickinessPolicyCreate, ReadWithoutTimeout: resourceAppCookieStickinessPolicyRead, DeleteWithoutTimeout: resourceAppCookieStickinessPolicyDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ - "name": { + "cookie_name": { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { - value := v.(string) - if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { - es = append(es, fmt.Errorf( - "only alphanumeric characters and hyphens allowed in %q", k)) - } - return - }, }, - - "load_balancer": { - Type: schema.TypeString, + "lb_port": { + Type: schema.TypeInt, Required: true, ForceNew: true, }, - - "lb_port": { - Type: schema.TypeInt, + "load_balancer": { + Type: schema.TypeString, Required: true, ForceNew: true, }, - - "cookie_name": { + "name": { Type: schema.TypeString, Required: true, ForceNew: true, + ValidateFunc: func(v interface{}, k string) (ws []string, es []error) { + value := v.(string) + if !regexp.MustCompile(`^[0-9A-Za-z-]+$`).MatchString(value) { + es = append(es, fmt.Errorf( + "only alphanumeric characters and hyphens allowed in %q", k)) + } + return + }, }, }, } @@ -69,165 +66,194 @@ func resourceAppCookieStickinessPolicyCreate(ctx context.Context, d *schema.Reso var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - // Provision the AppStickinessPolicy - acspOpts := &elb.CreateAppCookieStickinessPolicyInput{ - CookieName: aws.String(d.Get("cookie_name").(string)), - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - PolicyName: aws.String(d.Get("name").(string)), - } + lbName := d.Get("load_balancer").(string) + lbPort := d.Get("lb_port").(int) + policyName := d.Get("name").(string) + id := AppCookieStickinessPolicyCreateResourceID(lbName, lbPort, policyName) + { + input := &elb.CreateAppCookieStickinessPolicyInput{ + CookieName: aws.String(d.Get("cookie_name").(string)), + LoadBalancerName: aws.String(lbName), + PolicyName: aws.String(policyName), + } - if _, err := conn.CreateAppCookieStickinessPolicyWithContext(ctx, acspOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "creating AppCookieStickinessPolicy: %s", err) + if _, err := conn.CreateAppCookieStickinessPolicyWithContext(ctx, input); err != nil { + return sdkdiag.AppendErrorf(diags, "creating ELB Classic App Cookie Stickiness Policy (%s): %s", id, err) + } } - setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), - PolicyNames: []*string{aws.String(d.Get("name").(string))}, - } + { + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), + PolicyNames: aws.StringSlice([]string{policyName}), + } - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setLoadBalancerOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "setting AppCookieStickinessPolicy: %s", err) + if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input); err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic App Cookie Stickiness Policy (%s): %s", id, err) + } } - d.SetId(fmt.Sprintf("%s:%d:%s", - *acspOpts.LoadBalancerName, - *setLoadBalancerOpts.LoadBalancerPort, - *acspOpts.PolicyName)) - return diags + d.SetId(id) + + return append(diags, resourceAppCookieStickinessPolicyRead(ctx, d, meta)...) } func resourceAppCookieStickinessPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - lbName, lbPort, policyName := AppCookieStickinessPolicyParseID(d.Id()) + lbName, lbPort, policyName, err := AppCookieStickinessPolicyParseResourceID(d.Id()) - request := &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(lbName), - PolicyNames: []*string{aws.String(policyName)}, + if err != nil { + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - getResp, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, request) + policy, err := FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELB Classic App Cookie Stickiness Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodePolicyNotFoundException) { - log.Printf("[WARN] ELB Classic LB (%s) App Cookie Policy (%s) not found, removing from state", lbName, policyName) - d.SetId("") - return diags - } - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { - log.Printf("[WARN] ELB Classic LB (%s) not found, removing App Cookie Policy (%s) from state", lbName, policyName) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "retrieving ELB Classic (%s) App Cookie Policy (%s): %s", lbName, policyName, err) + return sdkdiag.AppendErrorf(diags, "reading ELB Classic App Cookie Stickiness Policy (%s): %s", d.Id(), err) } - if len(getResp.PolicyDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "Unable to find policy %#v", getResp.PolicyDescriptions) + if len(policy.PolicyAttributeDescriptions) != 1 || aws.StringValue(policy.PolicyAttributeDescriptions[0].AttributeName) != "CookieName" { + return sdkdiag.AppendErrorf(diags, "cookie not found") } + cookieAttr := policy.PolicyAttributeDescriptions[0] + d.Set("cookie_name", cookieAttr.AttributeValue) + d.Set("lb_port", lbPort) + d.Set("load_balancer", lbName) + d.Set("name", policyName) + + return diags +} + +func resourceAppCookieStickinessPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ELBConn() + + lbName, lbPort, policyName, err := AppCookieStickinessPolicyParseResourceID(d.Id()) - // we know the policy exists now, but we have to check if it's assigned to a listener - assigned, err := resourceSticknessPolicyAssigned(ctx, conn, policyName, lbName, lbPort) if err != nil { - return sdkdiag.AppendErrorf(diags, "reading ELB Classic App Cookie Stickiness Policy (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - if !d.IsNewResource() && !assigned { - log.Printf("[WARN] ELB Classic LB (%s) App Cookie Policy (%s) exists, but isn't assigned to a listener", lbName, policyName) - d.SetId("") - return diags + + // Perversely, if we Set an empty list of PolicyNames, we detach the + // policies attached to a listener, which is required to delete the + // policy itself. + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), + PolicyNames: aws.StringSlice([]string{}), } - // We can get away with this because there's only one attribute, the - // cookie expiration, in these descriptions. - policyDesc := getResp.PolicyDescriptions[0] - cookieAttr := policyDesc.PolicyAttributeDescriptions[0] - if aws.StringValue(cookieAttr.AttributeName) != "CookieName" { - return sdkdiag.AppendErrorf(diags, "Unable to find cookie Name.") + _, err = conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic App Cookie Stickiness Policy (%s): %s", d.Id(), err) } - d.Set("cookie_name", cookieAttr.AttributeValue) - d.Set("name", policyName) - d.Set("load_balancer", lbName) + log.Printf("[DEBUG] Deleting ELB Classic App Cookie Stickiness Policy: %s", d.Id()) + _, err = conn.DeleteLoadBalancerPolicyWithContext(ctx, &elb.DeleteLoadBalancerPolicyInput{ + LoadBalancerName: aws.String(lbName), + PolicyName: aws.String(policyName), + }) - lbPortInt, _ := strconv.Atoi(lbPort) - d.Set("lb_port", lbPortInt) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ELB Classic App Cookie Stickiness Policy (%s): %s", d.Id(), err) + } return diags } -// Determine if a particular policy is assigned to an ELB listener -func resourceSticknessPolicyAssigned(ctx context.Context, conn *elb.ELB, policyName, lbName, lbPort string) (bool, error) { - describeElbOpts := &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(lbName)}, +func FindLoadBalancerPolicyByTwoPartKey(ctx context.Context, conn *elb.ELB, lbName, policyName string) (*elb.PolicyDescription, error) { + input := &elb.DescribeLoadBalancerPoliciesInput{ + LoadBalancerName: aws.String(lbName), + PolicyNames: aws.StringSlice([]string{policyName}), } - describeResp, err := conn.DescribeLoadBalancersWithContext(ctx, describeElbOpts) - if tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { - return false, nil + output, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, elb.ErrCodePolicyNotFoundException, elb.ErrCodeAccessPointNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } } if err != nil { - return false, fmt.Errorf("retrieving LB: %s", err) + return nil, err } - if len(describeResp.LoadBalancerDescriptions) != 1 { - return false, errors.New("retrieving LB: empty response") + if output == nil || len(output.PolicyDescriptions) == 0 || output.PolicyDescriptions[0] == nil { + return nil, tfresource.NewEmptyResultError(input) } - lb := describeResp.LoadBalancerDescriptions[0] - assigned := false - for _, listener := range lb.ListenerDescriptions { - if listener == nil || listener.Listener == nil || lbPort != strconv.Itoa(int(aws.Int64Value(listener.Listener.LoadBalancerPort))) { + if count := len(output.PolicyDescriptions); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.PolicyDescriptions[0], nil +} + +func FindLoadBalancerListenerPolicyByThreePartKey(ctx context.Context, conn *elb.ELB, lbName string, lbPort int, policyName string) (*elb.PolicyDescription, error) { + policy, err := FindLoadBalancerPolicyByTwoPartKey(ctx, conn, lbName, policyName) + + if err != nil { + return nil, err + } + + lb, err := FindLoadBalancerByName(ctx, conn, lbName) + + if err != nil { + return nil, err + } + + for _, v := range lb.ListenerDescriptions { + if v == nil || v.Listener == nil { + continue + } + + if aws.Int64Value(v.Listener.LoadBalancerPort) != int64(lbPort) { continue } - for _, name := range listener.PolicyNames { - if policyName == aws.StringValue(name) { - assigned = true - break + for _, v := range v.PolicyNames { + if aws.StringValue(v) == policyName { + return policy, nil } } } - return assigned, nil + return nil, &resource.NotFoundError{} } -func resourceAppCookieStickinessPolicyDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ELBConn() +const appCookieStickinessPolicyResourceIDSeparator = ":" - lbName, _, policyName := AppCookieStickinessPolicyParseID(d.Id()) +func AppCookieStickinessPolicyCreateResourceID(lbName string, lbPort int, policyName string) string { + parts := []string{lbName, strconv.Itoa(lbPort), policyName} + id := strings.Join(parts, appCookieStickinessPolicyResourceIDSeparator) - // Perversely, if we Set an empty list of PolicyNames, we detach the - // policies attached to a listener, which is required to delete the - // policy itself. - setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), - PolicyNames: []*string{}, - } + return id +} - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setLoadBalancerOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "removing AppCookieStickinessPolicy: %s", err) - } +func AppCookieStickinessPolicyParseResourceID(id string) (string, int, string, error) { + parts := strings.Split(id, appCookieStickinessPolicyResourceIDSeparator) - request := &elb.DeleteLoadBalancerPolicyInput{ - LoadBalancerName: aws.String(lbName), - PolicyName: aws.String(policyName), - } + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + v, err := strconv.Atoi(parts[1]) + + if err != nil { + return "", 0, "", err + } - if _, err := conn.DeleteLoadBalancerPolicyWithContext(ctx, request); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting App stickiness policy %s: %s", d.Id(), err) + return parts[0], v, parts[2], nil } - return diags -} -// AppCookieStickinessPolicyParseID takes an ID and parses it into -// it's constituent parts. You need three axes (LB name, policy name, and LB -// port) to create or identify a stickiness policy in AWS's API. -func AppCookieStickinessPolicyParseID(id string) (string, string, string) { - parts := strings.SplitN(id, ":", 3) - return parts[0], parts[1], parts[2] + return "", 0, "", fmt.Errorf("unexpected format for ID (%[1]s), expected LBNAME%[2]sLBPORT%[2]sPOLICYNAME", id, appCookieStickinessPolicyResourceIDSeparator) } diff --git a/internal/service/elb/app_cookie_stickiness_policy_test.go b/internal/service/elb/app_cookie_stickiness_policy_test.go index bb0cee90a7d..e59e488b873 100644 --- a/internal/service/elb/app_cookie_stickiness_policy_test.go +++ b/internal/service/elb/app_cookie_stickiness_policy_test.go @@ -5,8 +5,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,11 +12,13 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfelb "github.com/hashicorp/terraform-provider-aws/internal/service/elb" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBAppCookieStickinessPolicy_basic(t *testing.T) { ctx := acctest.Context(t) - lbName := fmt.Sprintf("tf-test-lb-%s", sdkacctest.RandString(5)) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_app_cookie_stickiness_policy.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -27,35 +27,58 @@ func TestAccELBAppCookieStickinessPolicy_basic(t *testing.T) { CheckDestroy: testAccCheckAppCookieStickinessPolicyDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccAppCookieStickinessPolicyConfig_basic(lbName), + Config: testAccAppCookieStickinessPolicyConfig_basic(rName, "bourbon"), Check: resource.ComposeTestCheckFunc( - testAccCheckAppCookieStickinessPolicy(ctx, "aws_elb.lb", - "aws_app_cookie_stickiness_policy.foo", - ), + testAccCheckAppCookieStickinessPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "cookie_name", "bourbon"), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, { - ResourceName: "aws_app_cookie_stickiness_policy.foo", + ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, { - Config: testAccAppCookieStickinessPolicyConfig_update(lbName), + Config: testAccAppCookieStickinessPolicyConfig_basic(rName, "custard-cream"), Check: resource.ComposeTestCheckFunc( - testAccCheckAppCookieStickinessPolicy(ctx, "aws_elb.lb", - "aws_app_cookie_stickiness_policy.foo", - ), + testAccCheckAppCookieStickinessPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "cookie_name", "custard-cream"), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, }, }) } +func TestAccELBAppCookieStickinessPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_app_cookie_stickiness_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAppCookieStickinessPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAppCookieStickinessPolicyConfig_basic(rName, "bourbon"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAppCookieStickinessPolicyExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceAppCookieStickinessPolicy(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func TestAccELBAppCookieStickinessPolicy_Disappears_elb(t *testing.T) { ctx := acctest.Context(t) - lbName := fmt.Sprintf("tf-test-lb-%s", sdkacctest.RandString(5)) - elbResourceName := "aws_elb.lb" - resourceName := "aws_app_cookie_stickiness_policy.foo" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_app_cookie_stickiness_policy.test" + elbResourceName := "aws_elb.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -64,9 +87,9 @@ func TestAccELBAppCookieStickinessPolicy_Disappears_elb(t *testing.T) { CheckDestroy: testAccCheckAppCookieStickinessPolicyDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccAppCookieStickinessPolicyConfig_basic(lbName), + Config: testAccAppCookieStickinessPolicyConfig_basic(rName, "bourbon"), Check: resource.ComposeTestCheckFunc( - testAccCheckAppCookieStickinessPolicy(ctx, elbResourceName, resourceName), + testAccCheckAppCookieStickinessPolicyExists(ctx, resourceName), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceLoadBalancer(), elbResourceName), ), ExpectNonEmptyPlan: true, @@ -84,106 +107,58 @@ func testAccCheckAppCookieStickinessPolicyDestroy(ctx context.Context) resource. continue } - lbName, _, policyName := tfelb.AppCookieStickinessPolicyParseID( - rs.Primary.ID) - out, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(lbName), - PolicyNames: []*string{aws.String(policyName)}, - }) + lbName, lbPort, policyName, err := tfelb.AppCookieStickinessPolicyParseResourceID(rs.Primary.ID) + if err != nil { - if ec2err, ok := err.(awserr.Error); ok && (ec2err.Code() == "PolicyNotFound" || ec2err.Code() == "LoadBalancerNotFound") { - continue - } return err } - if len(out.PolicyDescriptions) > 0 { - return fmt.Errorf("Policy still exists") + _, err = tfelb.FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err } + + return fmt.Errorf("ELB Classic App Cookie Stickiness Policy %s still exists", rs.Primary.ID) } + return nil } } -func testAccCheckAppCookieStickinessPolicy(ctx context.Context, elbResource string, policyResource string) resource.TestCheckFunc { +func testAccCheckAppCookieStickinessPolicyExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[elbResource] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", elbResource) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") + return fmt.Errorf("No ELB Classic App Cookie Stickiness Policy ID is set") } - policy, ok := s.RootModule().Resources[policyResource] - if !ok { - return fmt.Errorf("Not found: %s", policyResource) + lbName, lbPort, policyName, err := tfelb.AppCookieStickinessPolicyParseResourceID(rs.Primary.ID) + + if err != nil { + return err } conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - elbName, _, policyName := tfelb.AppCookieStickinessPolicyParseID(policy.Primary.ID) - _, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(elbName), - PolicyNames: []*string{aws.String(policyName)}, - }) + + _, err = tfelb.FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) return err } } -func TestAccELBAppCookieStickinessPolicy_disappears(t *testing.T) { - ctx := acctest.Context(t) - lbName := fmt.Sprintf("tf-test-lb-%s", sdkacctest.RandString(5)) - elbResourceName := "aws_elb.lb" - resourceName := "aws_app_cookie_stickiness_policy.foo" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckAppCookieStickinessPolicyDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccAppCookieStickinessPolicyConfig_basic(lbName), - Check: resource.ComposeTestCheckFunc( - testAccCheckAppCookieStickinessPolicy(ctx, elbResourceName, resourceName), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceAppCookieStickinessPolicy(), resourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func testAccAppCookieStickinessPolicyConfig_basic(rName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_elb" "lb" { - name = "%s" - availability_zones = [data.aws_availability_zones.available.names[0]] - - listener { - instance_port = 8000 - instance_protocol = "http" - lb_port = 80 - lb_protocol = "http" - } -} - -resource "aws_app_cookie_stickiness_policy" "foo" { - name = "foo-policy" - load_balancer = aws_elb.lb.id - lb_port = 80 - cookie_name = "MyAppCookie" -} -`, rName)) -} - -// Change the cookie_name to "MyOtherAppCookie". -func testAccAppCookieStickinessPolicyConfig_update(rName string) string { +func testAccAppCookieStickinessPolicyConfig_basic(rName, cookieName string) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_elb" "lb" { - name = "%s" +resource "aws_elb" "test" { + name = %[1]q availability_zones = [data.aws_availability_zones.available.names[0]] listener { @@ -194,11 +169,11 @@ resource "aws_elb" "lb" { } } -resource "aws_app_cookie_stickiness_policy" "foo" { - name = "foo-policy" - load_balancer = aws_elb.lb.id +resource "aws_app_cookie_stickiness_policy" "test" { + name = %[1]q + load_balancer = aws_elb.test.id lb_port = 80 - cookie_name = "MyOtherAppCookie" + cookie_name = %[2]q } -`, rName)) +`, rName, cookieName)) } diff --git a/internal/service/elb/backend_server_policy.go b/internal/service/elb/backend_server_policy.go index b904604da21..b58e5302889 100644 --- a/internal/service/elb/backend_server_policy.go +++ b/internal/service/elb/backend_server_policy.go @@ -3,69 +3,69 @@ package elb import ( "context" "fmt" + "log" "strconv" "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" "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/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceBackendServerPolicy() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceBackendServerPolicyCreate, + CreateWithoutTimeout: resourceBackendServerPolicySet, ReadWithoutTimeout: resourceBackendServerPolicyRead, - UpdateWithoutTimeout: resourceBackendServerPolicyCreate, + UpdateWithoutTimeout: resourceBackendServerPolicySet, DeleteWithoutTimeout: resourceBackendServerPolicyDelete, Schema: map[string]*schema.Schema{ + "instance_port": { + Type: schema.TypeInt, + Required: true, + }, "load_balancer_name": { Type: schema.TypeString, Required: true, }, - "policy_names": { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, - Set: schema.HashString, - }, - - "instance_port": { - Type: schema.TypeInt, - Required: true, }, }, } } -func resourceBackendServerPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceBackendServerPolicySet(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName := d.Get("load_balancer_name") - - policyNames := []*string{} - if v, ok := d.GetOk("policy_names"); ok { - policyNames = flex.ExpandStringSet(v.(*schema.Set)) + instancePort := d.Get("instance_port").(int) + lbName := d.Get("load_balancer_name").(string) + id := BackendServerPolicyCreateResourceID(lbName, instancePort) + input := &elb.SetLoadBalancerPoliciesForBackendServerInput{ + InstancePort: aws.Int64(int64(instancePort)), + LoadBalancerName: aws.String(lbName), } - setOpts := &elb.SetLoadBalancerPoliciesForBackendServerInput{ - LoadBalancerName: aws.String(loadBalancerName.(string)), - InstancePort: aws.Int64(int64(d.Get("instance_port").(int))), - PolicyNames: policyNames, + if v, ok := d.GetOk("policy_names"); ok && v.(*schema.Set).Len() > 0 { + input.PolicyNames = flex.ExpandStringSet(v.(*schema.Set)) } - if _, err := conn.SetLoadBalancerPoliciesForBackendServerWithContext(ctx, setOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "setting LoadBalancerPoliciesForBackendServer: %s", err) + _, err := conn.SetLoadBalancerPoliciesForBackendServerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic Backend Server Policy (%s): %s", id, err) } - d.SetId(fmt.Sprintf("%s:%s", *setOpts.LoadBalancerName, strconv.FormatInt(*setOpts.InstancePort, 10))) + d.SetId(id) + return append(diags, resourceBackendServerPolicyRead(ctx, d, meta)...) } @@ -73,46 +73,27 @@ func resourceBackendServerPolicyRead(ctx context.Context, d *schema.ResourceData var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName, instancePort := BackendServerPoliciesParseID(d.Id()) - - describeElbOpts := &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(loadBalancerName)}, - } - - describeResp, err := conn.DescribeLoadBalancersWithContext(ctx, describeElbOpts) + lbName, instancePort, err := BackendServerPolicyParseResourceID(d.Id()) if err != nil { - if ec2err, ok := err.(awserr.Error); ok { - if ec2err.Code() == "LoadBalancerNotFound" { - d.SetId("") - return sdkdiag.AppendErrorf(diags, "LoadBalancerNotFound: %s", err) - } - } - return sdkdiag.AppendErrorf(diags, "retrieving ELB description: %s", err) - } - - if len(describeResp.LoadBalancerDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - lb := describeResp.LoadBalancerDescriptions[0] + policyNames, err := FindLoadBalancerBackendServerPolicyByTwoPartKey(ctx, conn, lbName, instancePort) - policyNames := []*string{} - for _, backendServer := range lb.BackendServerDescriptions { - if instancePort != strconv.Itoa(int(aws.Int64Value(backendServer.InstancePort))) { - continue - } - - policyNames = append(policyNames, backendServer.PolicyNames...) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELB Classic Backend Server Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - d.Set("load_balancer_name", loadBalancerName) - instancePortVal, err := strconv.ParseInt(instancePort, 10, 64) if err != nil { - return sdkdiag.AppendErrorf(diags, "parsing instance port: %s", err) + return sdkdiag.AppendErrorf(diags, "reading ELB Classic Backend Server Policy (%s): %s", d.Id(), err) } - d.Set("instance_port", instancePortVal) - d.Set("policy_names", flex.FlattenStringList(policyNames)) + + d.Set("instance_port", instancePort) + d.Set("load_balancer_name", lbName) + d.Set("policy_names", policyNames) return diags } @@ -121,27 +102,73 @@ func resourceBackendServerPolicyDelete(ctx context.Context, d *schema.ResourceDa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName, instancePort := BackendServerPoliciesParseID(d.Id()) + lbName, instancePort, err := BackendServerPolicyParseResourceID(d.Id()) - instancePortInt, err := strconv.ParseInt(instancePort, 10, 64) if err != nil { - return sdkdiag.AppendErrorf(diags, "parsing instancePort as integer: %s", err) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - setOpts := &elb.SetLoadBalancerPoliciesForBackendServerInput{ - LoadBalancerName: aws.String(loadBalancerName), - InstancePort: aws.Int64(instancePortInt), - PolicyNames: []*string{}, + input := &elb.SetLoadBalancerPoliciesForBackendServerInput{ + InstancePort: aws.Int64(int64(instancePort)), + LoadBalancerName: aws.String(lbName), + PolicyNames: aws.StringSlice([]string{}), } - if _, err := conn.SetLoadBalancerPoliciesForBackendServerWithContext(ctx, setOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "setting LoadBalancerPoliciesForBackendServer: %s", err) + log.Printf("[DEBUG] Deleting ELB Classic Backend Server Policy: %s", d.Id()) + _, err = conn.SetLoadBalancerPoliciesForBackendServerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic Backend Server Policy (%s): %s", d.Id(), err) } return diags } -func BackendServerPoliciesParseID(id string) (string, string) { - parts := strings.SplitN(id, ":", 2) - return parts[0], parts[1] +func FindLoadBalancerBackendServerPolicyByTwoPartKey(ctx context.Context, conn *elb.ELB, lbName string, instancePort int) ([]string, error) { + lb, err := FindLoadBalancerByName(ctx, conn, lbName) + + if err != nil { + return nil, err + } + + var policyNames []string + + for _, v := range lb.BackendServerDescriptions { + if v == nil { + continue + } + + if aws.Int64Value(v.InstancePort) != int64(instancePort) { + continue + } + + policyNames = append(policyNames, aws.StringValueSlice(v.PolicyNames)...) + } + + return policyNames, nil +} + +const backendServerPolicyResourceIDSeparator = ":" + +func BackendServerPolicyCreateResourceID(lbName string, instancePort int) string { + parts := []string{lbName, strconv.Itoa(instancePort)} + id := strings.Join(parts, backendServerPolicyResourceIDSeparator) + + return id +} + +func BackendServerPolicyParseResourceID(id string) (string, int, error) { + parts := strings.Split(id, backendServerPolicyResourceIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + v, err := strconv.Atoi(parts[1]) + + if err != nil { + return "", 0, err + } + + return parts[0], v, nil + } + + return "", 0, fmt.Errorf("unexpected format for ID (%[1]s), expected LBNAME%[2]sINSTANCEPORT", id, backendServerPolicyResourceIDSeparator) } diff --git a/internal/service/elb/backend_server_policy_test.go b/internal/service/elb/backend_server_policy_test.go index 395da930b79..5e7c5af6a43 100644 --- a/internal/service/elb/backend_server_policy_test.go +++ b/internal/service/elb/backend_server_policy_test.go @@ -5,27 +5,54 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" - "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" tfelb "github.com/hashicorp/terraform-provider-aws/internal/service/elb" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBBackendServerPolicy_basic(t *testing.T) { ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) privateKey1 := acctest.TLSRSAPrivateKeyPEM(t, 2048) + publicKey1 := acctest.TLSRSAPublicKeyPEM(t, privateKey1) privateKey2 := acctest.TLSRSAPrivateKeyPEM(t, 2048) + publicKey2 := acctest.TLSRSAPublicKeyPEM(t, privateKey2) + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, privateKey1, "example.com") + resourceName := "aws_load_balancer_backend_server_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckBackendServerPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccBackendServerPolicyConfig_basic(rName, privateKey1, certificate, publicKey1, publicKey2), + Check: resource.ComposeTestCheckFunc( + testAccCheckBackendServerPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_port", "443"), + resource.TestCheckResourceAttr(resourceName, "policy_names.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "policy_names.*", "aws_load_balancer_policy.test1", "policy_name"), + ), + }, + }, + }) +} + +func TestAccELBBackendServerPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + privateKey1 := acctest.TLSRSAPrivateKeyPEM(t, 2048) publicKey1 := acctest.TLSRSAPublicKeyPEM(t, privateKey1) + privateKey2 := acctest.TLSRSAPrivateKeyPEM(t, 2048) publicKey2 := acctest.TLSRSAPublicKeyPEM(t, privateKey2) - certificate1 := acctest.TLSRSAX509SelfSignedCertificatePEM(t, privateKey1, "example.com") - rString := sdkacctest.RandString(8) - lbName := fmt.Sprintf("tf-acc-lb-bsp-basic-%s", rString) + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, privateKey1, "example.com") + resourceName := "aws_load_balancer_backend_server_policy.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -34,141 +61,116 @@ func TestAccELBBackendServerPolicy_basic(t *testing.T) { CheckDestroy: testAccCheckBackendServerPolicyDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccBackendServerPolicyConfig_basic0(lbName, privateKey1, publicKey1, certificate1), + Config: testAccBackendServerPolicyConfig_basic(rName, privateKey1, certificate, publicKey1, publicKey2), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyState(ctx, "aws_elb.test-lb", "aws_load_balancer_policy.test-pubkey-policy0"), - testAccCheckPolicyState(ctx, "aws_elb.test-lb", "aws_load_balancer_policy.test-backend-auth-policy0"), - testAccCheckBackendServerPolicyState(ctx, lbName, "test-backend-auth-policy0", true), + testAccCheckBackendServerPolicyExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceBackendServerPolicy(), resourceName), ), + ExpectNonEmptyPlan: true, }, + }, + }) +} + +func TestAccELBBackendServerPolicy_update(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + privateKey1 := acctest.TLSRSAPrivateKeyPEM(t, 2048) + publicKey1 := acctest.TLSRSAPublicKeyPEM(t, privateKey1) + privateKey2 := acctest.TLSRSAPrivateKeyPEM(t, 2048) + publicKey2 := acctest.TLSRSAPublicKeyPEM(t, privateKey2) + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, privateKey1, "example.com") + resourceName := "aws_load_balancer_backend_server_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckBackendServerPolicyDestroy(ctx), + Steps: []resource.TestStep{ { - Config: testAccBackendServerPolicyConfig_basic1(lbName, privateKey1, publicKey1, certificate1, publicKey2), + Config: testAccBackendServerPolicyConfig_basic(rName, privateKey1, certificate, publicKey1, publicKey2), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyState(ctx, "aws_elb.test-lb", "aws_load_balancer_policy.test-pubkey-policy0"), - testAccCheckPolicyState(ctx, "aws_elb.test-lb", "aws_load_balancer_policy.test-pubkey-policy1"), - testAccCheckPolicyState(ctx, "aws_elb.test-lb", "aws_load_balancer_policy.test-backend-auth-policy0"), - testAccCheckBackendServerPolicyState(ctx, lbName, "test-backend-auth-policy0", true), + testAccCheckBackendServerPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_port", "443"), + resource.TestCheckResourceAttr(resourceName, "policy_names.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "policy_names.*", "aws_load_balancer_policy.test1", "policy_name"), ), }, { - Config: testAccBackendServerPolicyConfig_basic2(lbName, privateKey1, certificate1), + Config: testAccBackendServerPolicyConfig_update(rName, privateKey1, certificate, publicKey1, publicKey2), Check: resource.ComposeTestCheckFunc( - testAccCheckBackendServerPolicyState(ctx, lbName, "test-backend-auth-policy0", false), + testAccCheckBackendServerPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "instance_port", "443"), + resource.TestCheckResourceAttr(resourceName, "policy_names.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "policy_names.*", "aws_load_balancer_policy.test3", "policy_name"), ), }, }, }) } -func policyInBackendServerPolicies(str string, list []string) bool { - for _, v := range list { - if v == str { - return true - } - } - return false -} - func testAccCheckBackendServerPolicyDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() for _, rs := range s.RootModule().Resources { - switch { - case rs.Type == "aws_load_balancer_policy": - loadBalancerName, policyName := tfelb.BackendServerPoliciesParseID(rs.Primary.ID) - out, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(loadBalancerName), - PolicyNames: []*string{aws.String(policyName)}, - }) - if err != nil { - if ec2err, ok := err.(awserr.Error); ok && (ec2err.Code() == "PolicyNotFound" || ec2err.Code() == "LoadBalancerNotFound") { - continue - } - return err - } - if len(out.PolicyDescriptions) > 0 { - return fmt.Errorf("Policy still exists") - } - case rs.Type == "aws_load_balancer_backend_policy": - loadBalancerName, policyName := tfelb.BackendServerPoliciesParseID(rs.Primary.ID) - out, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(loadBalancerName)}, - }) - - if tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { - continue - } - - if err != nil { - return err - } - - for _, backendServer := range out.LoadBalancerDescriptions[0].BackendServerDescriptions { - policyStrings := []string{} - for _, pol := range backendServer.PolicyNames { - policyStrings = append(policyStrings, *pol) - } - if policyInBackendServerPolicies(policyName, policyStrings) { - return fmt.Errorf("Policy still exists and is assigned") - } - } - default: + if rs.Type != "aws_load_balancer_backend_policy" { continue } - } - return nil - } -} -func testAccCheckBackendServerPolicyState(ctx context.Context, loadBalancerName string, loadBalancerBackendAuthPolicyName string, assigned bool) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() + lbName, instancePort, err := tfelb.BackendServerPolicyParseResourceID(rs.Primary.ID) - loadBalancerDescription, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(loadBalancerName)}, - }) - if err != nil { - return err - } + if err != nil { + return err + } + + _, err = tfelb.FindLoadBalancerBackendServerPolicyByTwoPartKey(ctx, conn, lbName, instancePort) - for _, backendServer := range loadBalancerDescription.LoadBalancerDescriptions[0].BackendServerDescriptions { - policyStrings := []string{} - for _, pol := range backendServer.PolicyNames { - policyStrings = append(policyStrings, *pol) + if tfresource.NotFound(err) { + continue } - if policyInBackendServerPolicies(loadBalancerBackendAuthPolicyName, policyStrings) != assigned { - if assigned { - return fmt.Errorf("Policy no longer assigned %s not in %+v", loadBalancerBackendAuthPolicyName, policyStrings) - } else { - return fmt.Errorf("Policy exists and is assigned") - } + + if err != nil { + return err } + + return fmt.Errorf("ELB Classic Backend Server Policy %s still exists", rs.Primary.ID) } return nil } } -func testAccBackendServerPolicyConfig_basic0(rName, privateKey, publicKey, certificate string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" +func testAccCheckBackendServerPolicyExists(ctx context.Context, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} + if rs.Primary.ID == "" { + return fmt.Errorf("No ELB Classic Backend Server Policy ID is set") + } -resource "aws_iam_server_certificate" "test-iam-cert0" { - name_prefix = "test_cert_" - certificate_body = "%[2]s" - private_key = "%[3]s" + lbName, instancePort, err := tfelb.BackendServerPolicyParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() + + _, err = tfelb.FindLoadBalancerBackendServerPolicyByTwoPartKey(ctx, conn, lbName, instancePort) + + return err + } } -resource "aws_elb" "test-lb" { - name = "%[1]s" +func testAccBackendServerPolicyConfig_base(rName, privateKey, certificate, publicKey1, publicKey2 string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` +resource "aws_elb" "test" { + name = %[1]q availability_zones = [data.aws_availability_zones.available.names[0]] listener { @@ -176,157 +178,90 @@ resource "aws_elb" "test-lb" { instance_protocol = "https" lb_port = 443 lb_protocol = "https" - ssl_certificate_id = aws_iam_server_certificate.test-iam-cert0.arn + ssl_certificate_id = aws_iam_server_certificate.test.arn } +} - tags = { - Name = "tf-acc-test" - } +resource "aws_iam_server_certificate" "test" { + name = %[1]q + certificate_body = "%[2]s" + private_key = "%[3]s" } -resource "aws_load_balancer_policy" "test-pubkey-policy0" { - load_balancer_name = aws_elb.test-lb.name - policy_name = "test-pubkey-policy0" +resource "aws_load_balancer_policy" "test0" { + load_balancer_name = aws_elb.test.name + policy_name = "%[1]s-0" policy_type_name = "PublicKeyPolicyType" policy_attribute { name = "PublicKey" - value = replace(replace(replace("%[4]s", "\n", ""), "-----BEGIN PUBLIC KEY-----", ""), "-----END PUBLIC KEY-----", "") + value = "%[4]s" } } -resource "aws_load_balancer_policy" "test-backend-auth-policy0" { - load_balancer_name = aws_elb.test-lb.name - policy_name = "test-backend-auth-policy0" +resource "aws_load_balancer_policy" "test1" { + load_balancer_name = aws_elb.test.name + policy_name = "%[1]s-1" policy_type_name = "BackendServerAuthenticationPolicyType" policy_attribute { name = "PublicKeyPolicyName" - value = aws_load_balancer_policy.test-pubkey-policy0.policy_name - } -} - -resource "aws_load_balancer_backend_server_policy" "test-backend-auth-policies-443" { - load_balancer_name = aws_elb.test-lb.name - instance_port = 443 - - policy_names = [ - aws_load_balancer_policy.test-backend-auth-policy0.policy_name, - ] -} -`, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(privateKey), acctest.TLSPEMEscapeNewlines(publicKey)) -} - -func testAccBackendServerPolicyConfig_basic1(rName, privateKey1, publicKey1, certificate1, publicKey2 string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_iam_server_certificate" "test-iam-cert0" { - name_prefix = "test_cert_" - certificate_body = "%[2]s" - private_key = "%[3]s" -} - -resource "aws_elb" "test-lb" { - name = "%[1]s" - availability_zones = [data.aws_availability_zones.available.names[0]] - - listener { - instance_port = 443 - instance_protocol = "https" - lb_port = 443 - lb_protocol = "https" - ssl_certificate_id = aws_iam_server_certificate.test-iam-cert0.arn - } - - tags = { - Name = "tf-acc-test" - } -} - -resource "aws_load_balancer_policy" "test-pubkey-policy0" { - load_balancer_name = aws_elb.test-lb.name - policy_name = "test-pubkey-policy0" - policy_type_name = "PublicKeyPolicyType" - - policy_attribute { - name = "PublicKey" - value = replace(replace(replace("%[4]s", "\n", ""), "-----BEGIN PUBLIC KEY-----", ""), "-----END PUBLIC KEY-----", "") + value = aws_load_balancer_policy.test0.policy_name } } -resource "aws_load_balancer_policy" "test-pubkey-policy1" { - load_balancer_name = aws_elb.test-lb.name - policy_name = "test-pubkey-policy1" +resource "aws_load_balancer_policy" "test2" { + load_balancer_name = aws_elb.test.name + policy_name = "%[1]s-2" policy_type_name = "PublicKeyPolicyType" policy_attribute { name = "PublicKey" - value = replace(replace(replace("%[5]s", "\n", ""), "-----BEGIN PUBLIC KEY-----", ""), "-----END PUBLIC KEY-----", "") + value = "%[5]s" } } -resource "aws_load_balancer_policy" "test-backend-auth-policy0" { - load_balancer_name = aws_elb.test-lb.name - policy_name = "test-backend-auth-policy0" +resource "aws_load_balancer_policy" "test3" { + load_balancer_name = aws_elb.test.name + policy_name = "%[1]s-3" policy_type_name = "BackendServerAuthenticationPolicyType" policy_attribute { name = "PublicKeyPolicyName" - value = aws_load_balancer_policy.test-pubkey-policy1.policy_name + value = aws_load_balancer_policy.test2.policy_name } } +`, + rName, + acctest.TLSPEMEscapeNewlines(certificate), + acctest.TLSPEMEscapeNewlines(privateKey), + acctest.TLSPEMRemovePublicKeyEncapsulationBoundaries(acctest.TLSPEMRemoveNewlines(publicKey1)), + acctest.TLSPEMRemovePublicKeyEncapsulationBoundaries(acctest.TLSPEMRemoveNewlines(publicKey2)), + )) +} -resource "aws_load_balancer_backend_server_policy" "test-backend-auth-policies-443" { - load_balancer_name = aws_elb.test-lb.name +func testAccBackendServerPolicyConfig_basic(rName, privateKey, certificate, publicKey1, publicKey2 string) string { + return acctest.ConfigCompose(testAccBackendServerPolicyConfig_base(rName, privateKey, certificate, publicKey1, publicKey2), ` +resource "aws_load_balancer_backend_server_policy" "test" { + load_balancer_name = aws_elb.test.name instance_port = 443 policy_names = [ - aws_load_balancer_policy.test-backend-auth-policy0.policy_name, + aws_load_balancer_policy.test1.policy_name, ] } -`, rName, acctest.TLSPEMEscapeNewlines(certificate1), acctest.TLSPEMEscapeNewlines(privateKey1), acctest.TLSPEMEscapeNewlines(publicKey1), acctest.TLSPEMEscapeNewlines(publicKey2)) -} - -func testAccBackendServerPolicyConfig_basic2(rName, privateKey, certificate string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - -resource "aws_iam_server_certificate" "test-iam-cert0" { - name_prefix = "test_cert_" - certificate_body = "%[2]s" - private_key = "%[3]s" +`) } -resource "aws_elb" "test-lb" { - name = "%[1]s" - availability_zones = [data.aws_availability_zones.available.names[0]] - - listener { - instance_port = 443 - instance_protocol = "https" - lb_port = 443 - lb_protocol = "https" - ssl_certificate_id = aws_iam_server_certificate.test-iam-cert0.arn - } +func testAccBackendServerPolicyConfig_update(rName, privateKey, certificate, publicKey1, publicKey2 string) string { + return acctest.ConfigCompose(testAccBackendServerPolicyConfig_base(rName, privateKey, certificate, publicKey1, publicKey2), ` +resource "aws_load_balancer_backend_server_policy" "test" { + load_balancer_name = aws_elb.test.name + instance_port = 443 - tags = { - Name = "tf-acc-test" - } + policy_names = [ + aws_load_balancer_policy.test3.policy_name, + ] } -`, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(privateKey)) +`) } diff --git a/internal/service/elb/lb_cookie_stickiness_policy.go b/internal/service/elb/lb_cookie_stickiness_policy.go index bb5205a746c..56aae03c67d 100644 --- a/internal/service/elb/lb_cookie_stickiness_policy.go +++ b/internal/service/elb/lb_cookie_stickiness_policy.go @@ -9,47 +9,42 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elb" - "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-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceCookieStickinessPolicy() *schema.Resource { return &schema.Resource{ - // There is no concept of "updating" an LB Stickiness policy in - // the AWS API. CreateWithoutTimeout: resourceCookieStickinessPolicyCreate, ReadWithoutTimeout: resourceCookieStickinessPolicyRead, DeleteWithoutTimeout: resourceCookieStickinessPolicyDelete, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, + "cookie_expiration_period": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + ValidateFunc: validation.IntAtLeast(0), + }, + "lb_port": { + Type: schema.TypeInt, Required: true, ForceNew: true, }, - "load_balancer": { Type: schema.TypeString, Required: true, ForceNew: true, }, - - "lb_port": { - Type: schema.TypeInt, + "name": { + Type: schema.TypeString, Required: true, ForceNew: true, }, - - "cookie_expiration_period": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, - ValidateFunc: validation.IntAtLeast(0), - }, }, } } @@ -58,101 +53,79 @@ func resourceCookieStickinessPolicyCreate(ctx context.Context, d *schema.Resourc var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - // Provision the LBStickinessPolicy - lbspOpts := &elb.CreateLBCookieStickinessPolicyInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - PolicyName: aws.String(d.Get("name").(string)), - } + lbName := d.Get("load_balancer").(string) + lbPort := d.Get("lb_port").(int) + policyName := d.Get("name").(string) + id := LBCookieStickinessPolicyCreateResourceID(lbName, lbPort, policyName) + { + input := &elb.CreateLBCookieStickinessPolicyInput{ + LoadBalancerName: aws.String(lbName), + PolicyName: aws.String(policyName), + } - if v := d.Get("cookie_expiration_period").(int); v > 0 { - lbspOpts.CookieExpirationPeriod = aws.Int64(int64(v)) - } + if v, ok := d.GetOk("cookie_expiration_period"); ok { + input.CookieExpirationPeriod = aws.Int64(int64(v.(int))) + } - log.Printf("[DEBUG] LB Cookie Stickiness Policy opts: %#v", lbspOpts) - if _, err := conn.CreateLBCookieStickinessPolicyWithContext(ctx, lbspOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "creating LBCookieStickinessPolicy: %s", err) - } + _, err := conn.CreateLBCookieStickinessPolicyWithContext(ctx, input) - setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), - PolicyNames: []*string{aws.String(d.Get("name").(string))}, + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating ELB Classic LB Cookie Stickiness Policy (%s): %s", id, err) + } } - log.Printf("[DEBUG] LB Cookie Stickiness create configuration: %#v", setLoadBalancerOpts) - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setLoadBalancerOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "setting LBCookieStickinessPolicy: %s", err) + { + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), + PolicyNames: aws.StringSlice([]string{policyName}), + } + + _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic LB Cookie Stickiness Policy (%s): %s", id, err) + } } - d.SetId(fmt.Sprintf("%s:%d:%s", - *lbspOpts.LoadBalancerName, - *setLoadBalancerOpts.LoadBalancerPort, - *lbspOpts.PolicyName)) - return diags + d.SetId(id) + + return append(diags, resourceCookieStickinessPolicyRead(ctx, d, meta)...) } func resourceCookieStickinessPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - lbName, lbPort, policyName := CookieStickinessPolicyParseID(d.Id()) + lbName, lbPort, policyName, err := LBCookieStickinessPolicyParseResourceID(d.Id()) - request := &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(lbName), - PolicyNames: []*string{aws.String(policyName)}, - } - - getResp, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, request) if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodePolicyNotFoundException) { - log.Printf("[WARN] ELB Classic LB (%s) LB Cookie Policy (%s) not found, removing from state", lbName, policyName) - d.SetId("") - return diags - } - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { - log.Printf("[WARN] ELB Classic LB (%s) not found, removing from state", lbName) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "retrieving ELB Classic (%s) LB Cookie Policy (%s): %s", lbName, policyName, err) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - if len(getResp.PolicyDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "Unable to find policy %#v", getResp.PolicyDescriptions) + policy, err := FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELB Classic LB Cookie Stickiness Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - // we know the policy exists now, but we have to check if it's assigned to a listener - assigned, err := resourceSticknessPolicyAssigned(ctx, conn, policyName, lbName, lbPort) if err != nil { return sdkdiag.AppendErrorf(diags, "reading ELB Classic LB Cookie Stickiness Policy (%s): %s", d.Id(), err) } - if !d.IsNewResource() && !assigned { - // policy exists, but isn't assigned to a listener - log.Printf("[WARN] ELB Classic LB (%s) LB Cookie Policy (%s) exists, but isn't assigned to a listener", lbName, policyName) - d.SetId("") - return diags - } - // We can get away with this because there's only one attribute, the - // cookie expiration, in these descriptions. - policyDesc := getResp.PolicyDescriptions[0] - cookieAttr := policyDesc.PolicyAttributeDescriptions[0] - if aws.StringValue(cookieAttr.AttributeName) != "CookieExpirationPeriod" { - return sdkdiag.AppendErrorf(diags, "Unable to find cookie expiration period.") + if len(policy.PolicyAttributeDescriptions) != 1 || aws.StringValue(policy.PolicyAttributeDescriptions[0].AttributeName) != "CookieExpirationPeriod" { + return sdkdiag.AppendErrorf(diags, "cookie expiration period not found") } - cookieVal, err := strconv.Atoi(aws.StringValue(cookieAttr.AttributeValue)) - if err != nil { + if v, err := strconv.Atoi(aws.StringValue(policy.PolicyAttributeDescriptions[0].AttributeValue)); err != nil { return sdkdiag.AppendErrorf(diags, "parsing cookie expiration period: %s", err) + } else { + d.Set("cookie_expiration_period", v) } - d.Set("cookie_expiration_period", cookieVal) - - d.Set("name", policyName) + d.Set("lb_port", lbPort) d.Set("load_balancer", lbName) - lbPortInt, err := strconv.Atoi(lbPort) - if err != nil { - return sdkdiag.AppendErrorf(diags, "reading ELB Classic LB Cookie Stickiness Policy (%s): parsing port number: %s", d.Id(), err) - } - d.Set("lb_port", lbPortInt) + d.Set("name", policyName) return diags } @@ -161,36 +134,61 @@ func resourceCookieStickinessPolicyDelete(ctx context.Context, d *schema.Resourc var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - lbName, _, policyName := CookieStickinessPolicyParseID(d.Id()) + lbName, lbPort, policyName, err := LBCookieStickinessPolicyParseResourceID(d.Id()) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) + } // Perversely, if we Set an empty list of PolicyNames, we detach the // policies attached to a listener, which is required to delete the // policy itself. - setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), - PolicyNames: []*string{}, + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), + PolicyNames: aws.StringSlice([]string{}), } - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setLoadBalancerOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "removing LBCookieStickinessPolicy: %s", err) + _, err = conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic LB Cookie Stickiness Policy (%s): %s", d.Id(), err) } - request := &elb.DeleteLoadBalancerPolicyInput{ + log.Printf("[DEBUG] Deleting ELB Classic LB Cookie Stickiness Policy: %s", d.Id()) + _, err = conn.DeleteLoadBalancerPolicyWithContext(ctx, &elb.DeleteLoadBalancerPolicyInput{ LoadBalancerName: aws.String(lbName), PolicyName: aws.String(policyName), - } + }) - if _, err := conn.DeleteLoadBalancerPolicyWithContext(ctx, request); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting LB stickiness policy %s: %s", d.Id(), err) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ELB Classic LB Cookie Stickiness Policy (%s): %s", d.Id(), err) } + return diags } -// CookieStickinessPolicyParseID takes an ID and parses it into -// it's constituent parts. You need three axes (LB name, policy name, and LB -// port) to create or identify a stickiness policy in AWS's API. -func CookieStickinessPolicyParseID(id string) (string, string, string) { - parts := strings.SplitN(id, ":", 3) - return parts[0], parts[1], parts[2] +const lbCookieStickinessPolicyResourceIDSeparator = ":" + +func LBCookieStickinessPolicyCreateResourceID(lbName string, lbPort int, policyName string) string { + parts := []string{lbName, strconv.Itoa(lbPort), policyName} + id := strings.Join(parts, lbCookieStickinessPolicyResourceIDSeparator) + + return id +} + +func LBCookieStickinessPolicyParseResourceID(id string) (string, int, string, error) { + parts := strings.Split(id, lbCookieStickinessPolicyResourceIDSeparator) + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + v, err := strconv.Atoi(parts[1]) + + if err != nil { + return "", 0, "", err + } + + return parts[0], v, parts[2], nil + } + + return "", 0, "", fmt.Errorf("unexpected format for ID (%[1]s), expected LBNAME%[2]sLBPORT%[2]sPOLICYNAME", id, lbCookieStickinessPolicyResourceIDSeparator) } diff --git a/internal/service/elb/lb_cookie_stickiness_policy_test.go b/internal/service/elb/lb_cookie_stickiness_policy_test.go index 8001e835763..8b578e10e7b 100644 --- a/internal/service/elb/lb_cookie_stickiness_policy_test.go +++ b/internal/service/elb/lb_cookie_stickiness_policy_test.go @@ -5,8 +5,6 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,12 +12,14 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfelb "github.com/hashicorp/terraform-provider-aws/internal/service/elb" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBCookieStickinessPolicy_basic(t *testing.T) { ctx := acctest.Context(t) - lbName := fmt.Sprintf("tf-test-lb-%s", sdkacctest.RandString(5)) - resourceName := "aws_lb_cookie_stickiness_policy.foo" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_cookie_stickiness_policy.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), @@ -27,89 +27,29 @@ func TestAccELBCookieStickinessPolicy_basic(t *testing.T) { CheckDestroy: testAccCheckLBCookieStickinessPolicyDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccLBCookieStickinessPolicyConfig_basic(lbName), + Config: testAccLBCookieStickinessPolicyConfig_basic(rName, 300), Check: resource.ComposeTestCheckFunc( + testAccCheckLBCookieStickinessPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "cookie_expiration_period", "300"), - testAccCheckLBCookieStickinessPolicy(ctx, "aws_elb.lb", - "aws_lb_cookie_stickiness_policy.foo", - ), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, { - Config: testAccLBCookieStickinessPolicyConfig_update(lbName), + Config: testAccLBCookieStickinessPolicyConfig_basic(rName, 0), Check: resource.ComposeTestCheckFunc( + testAccCheckLBCookieStickinessPolicyExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "cookie_expiration_period", "0"), - testAccCheckLBCookieStickinessPolicy(ctx, "aws_elb.lb", - "aws_lb_cookie_stickiness_policy.foo", - ), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, }, }) } -func testAccCheckLBCookieStickinessPolicyDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_lb_cookie_stickiness_policy" { - continue - } - - lbName, _, policyName := tfelb.CookieStickinessPolicyParseID(rs.Primary.ID) - out, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(lbName), - PolicyNames: []*string{aws.String(policyName)}, - }) - if err != nil { - if ec2err, ok := err.(awserr.Error); ok && (ec2err.Code() == "PolicyNotFound" || ec2err.Code() == "LoadBalancerNotFound") { - continue - } - return err - } - - if len(out.PolicyDescriptions) > 0 { - return fmt.Errorf("Policy still exists") - } - } - - return nil - } -} - -func testAccCheckLBCookieStickinessPolicy(ctx context.Context, elbResource string, policyResource string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[elbResource] - if !ok { - return fmt.Errorf("Not found: %s", elbResource) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - policy, ok := s.RootModule().Resources[policyResource] - if !ok { - return fmt.Errorf("Not found: %s", policyResource) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - elbName, _, policyName := tfelb.CookieStickinessPolicyParseID(policy.Primary.ID) - _, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(elbName), - PolicyNames: []*string{aws.String(policyName)}, - }) - - return err - } -} - func TestAccELBCookieStickinessPolicy_disappears(t *testing.T) { ctx := acctest.Context(t) - lbName := fmt.Sprintf("tf-test-lb-%s", sdkacctest.RandString(5)) - elbResourceName := "aws_elb.lb" - resourceName := "aws_lb_cookie_stickiness_policy.foo" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_cookie_stickiness_policy.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -118,9 +58,9 @@ func TestAccELBCookieStickinessPolicy_disappears(t *testing.T) { CheckDestroy: testAccCheckLBCookieStickinessPolicyDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccLBCookieStickinessPolicyConfig_basic(lbName), + Config: testAccLBCookieStickinessPolicyConfig_basic(rName, 300), Check: resource.ComposeTestCheckFunc( - testAccCheckLBCookieStickinessPolicy(ctx, elbResourceName, resourceName), + testAccCheckLBCookieStickinessPolicyExists(ctx, resourceName), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceCookieStickinessPolicy(), resourceName), ), ExpectNonEmptyPlan: true, @@ -131,9 +71,9 @@ func TestAccELBCookieStickinessPolicy_disappears(t *testing.T) { func TestAccELBCookieStickinessPolicy_Disappears_elb(t *testing.T) { ctx := acctest.Context(t) - lbName := fmt.Sprintf("tf-test-lb-%s", sdkacctest.RandString(5)) - elbResourceName := "aws_elb.lb" - resourceName := "aws_lb_cookie_stickiness_policy.foo" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_lb_cookie_stickiness_policy.test" + elbResourceName := "aws_elb.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -142,9 +82,9 @@ func TestAccELBCookieStickinessPolicy_Disappears_elb(t *testing.T) { CheckDestroy: testAccCheckLBCookieStickinessPolicyDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccLBCookieStickinessPolicyConfig_basic(lbName), + Config: testAccLBCookieStickinessPolicyConfig_basic(rName, 300), Check: resource.ComposeTestCheckFunc( - testAccCheckLBCookieStickinessPolicy(ctx, elbResourceName, resourceName), + testAccCheckLBCookieStickinessPolicyExists(ctx, resourceName), acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceLoadBalancer(), elbResourceName), ), ExpectNonEmptyPlan: true, @@ -153,34 +93,67 @@ func TestAccELBCookieStickinessPolicy_Disappears_elb(t *testing.T) { }) } -func testAccLBCookieStickinessPolicyConfig_basic(rName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_elb" "lb" { - name = "%s" - availability_zones = [data.aws_availability_zones.available.names[0]] +func testAccCheckLBCookieStickinessPolicyDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - listener { - instance_port = 8000 - instance_protocol = "http" - lb_port = 80 - lb_protocol = "http" - } -} + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_lb_cookie_stickiness_policy" { + continue + } -resource "aws_lb_cookie_stickiness_policy" "foo" { - name = "foo-policy" - load_balancer = aws_elb.lb.id - lb_port = 80 - cookie_expiration_period = 300 + lbName, lbPort, policyName, err := tfelb.LBCookieStickinessPolicyParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfelb.FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("ELB Classic LB Cookie Stickiness Policy %s still exists", rs.Primary.ID) + } + + return nil + } } -`, rName)) + +func testAccCheckLBCookieStickinessPolicyExists(ctx context.Context, n string) 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 ELB Classic LB Cookie Stickiness Policy ID is set") + } + + lbName, lbPort, policyName, err := tfelb.LBCookieStickinessPolicyParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() + + _, err = tfelb.FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) + + return err + } } -// Sets the cookie_expiration_period to 0s. -func testAccLBCookieStickinessPolicyConfig_update(rName string) string { +func testAccLBCookieStickinessPolicyConfig_basic(rName string, expirationPeriod int) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_elb" "lb" { - name = "%s" +resource "aws_elb" "test" { + name = %[1]q availability_zones = [data.aws_availability_zones.available.names[0]] listener { @@ -191,11 +164,11 @@ resource "aws_elb" "lb" { } } -resource "aws_lb_cookie_stickiness_policy" "foo" { - name = "foo-policy" - load_balancer = aws_elb.lb.id +resource "aws_lb_cookie_stickiness_policy" "test" { + name = %[1]q + load_balancer = aws_elb.test.id lb_port = 80 - cookie_expiration_period = 0 + cookie_expiration_period = %[2]d } -`, rName)) +`, rName, expirationPeriod)) } diff --git a/internal/service/elb/lb_ssl_negotiation_policy.go b/internal/service/elb/lb_ssl_negotiation_policy.go index 1a482bdd4f7..98605224409 100644 --- a/internal/service/elb/lb_ssl_negotiation_policy.go +++ b/internal/service/elb/lb_ssl_negotiation_policy.go @@ -1,7 +1,6 @@ package elb import ( - "bytes" "context" "fmt" "log" @@ -10,41 +9,20 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elb" - "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/create" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceSSLNegotiationPolicy() *schema.Resource { return &schema.Resource{ - // There is no concept of "updating" an LB policy in - // the AWS API. CreateWithoutTimeout: resourceSSLNegotiationPolicyCreate, ReadWithoutTimeout: resourceSSLNegotiationPolicyRead, DeleteWithoutTimeout: resourceSSLNegotiationPolicyDelete, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "load_balancer": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "lb_port": { - Type: schema.TypeInt, - Required: true, - ForceNew: true, - }, - "attribute": { Type: schema.TypeSet, Optional: true, @@ -55,19 +33,27 @@ func ResourceSSLNegotiationPolicy() *schema.Resource { Type: schema.TypeString, Required: true, }, - "value": { Type: schema.TypeString, Required: true, }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["name"].(string))) - return create.StringHashcode(buf.String()) - }, + }, + "lb_port": { + Type: schema.TypeInt, + Required: true, + ForceNew: true, + }, + "load_balancer": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, }, } @@ -77,77 +63,73 @@ func resourceSSLNegotiationPolicyCreate(ctx context.Context, d *schema.ResourceD var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - // Provision the SSLNegotiationPolicy - lbspOpts := &elb.CreateLoadBalancerPolicyInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - PolicyName: aws.String(d.Get("name").(string)), - PolicyTypeName: aws.String("SSLNegotiationPolicyType"), - } + lbName := d.Get("load_balancer").(string) + lbPort := d.Get("lb_port").(int) + policyName := d.Get("name").(string) + id := SSLNegotiationPolicyCreateResourceID(lbName, lbPort, policyName) - // Check for Policy Attributes - if v, ok := d.GetOk("attribute"); ok { - // Expand the "attribute" set to aws-sdk-go compat []*elb.PolicyAttribute - lbspOpts.PolicyAttributes = ExpandPolicyAttributes(v.(*schema.Set).List()) - } + { + input := &elb.CreateLoadBalancerPolicyInput{ + LoadBalancerName: aws.String(lbName), + PolicyName: aws.String(policyName), + PolicyTypeName: aws.String("SSLNegotiationPolicyType"), + } - log.Printf("[DEBUG] Load Balancer Policy opts: %#v", lbspOpts) - if _, err := conn.CreateLoadBalancerPolicyWithContext(ctx, lbspOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "creating Load Balancer Policy: %s", err) - } + if v, ok := d.GetOk("attribute"); ok && v.(*schema.Set).Len() > 0 { + input.PolicyAttributes = ExpandPolicyAttributes(v.(*schema.Set).List()) + } + + _, err := conn.CreateLoadBalancerPolicyWithContext(ctx, input) - setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), - PolicyNames: []*string{aws.String(d.Get("name").(string))}, + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating ELB Classic SSL Negotiation Policy (%s): %s", id, err) + } } - log.Printf("[DEBUG] SSL Negotiation create configuration: %#v", setLoadBalancerOpts) - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setLoadBalancerOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "setting SSLNegotiationPolicy: %s", err) + { + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), + PolicyNames: aws.StringSlice([]string{policyName}), + } + + _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic SSL Negotiation Policy (%s): %s", id, err) + } } - d.SetId(fmt.Sprintf("%s:%d:%s", - *lbspOpts.LoadBalancerName, - *setLoadBalancerOpts.LoadBalancerPort, - *lbspOpts.PolicyName)) - return diags + d.SetId(id) + + return append(diags, resourceSSLNegotiationPolicyRead(ctx, d, meta)...) } func resourceSSLNegotiationPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - lbName, lbPort, policyName, err := SSLNegotiationPolicyParseID(d.Id()) + lbName, lbPort, policyName, err := SSLNegotiationPolicyParseResourceID(d.Id()) + if err != nil { - return sdkdiag.AppendErrorf(diags, "reading ELB Classic (%s) SSL Negotiation Policy: %s", lbName, err) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - request := &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(lbName), - PolicyNames: []*string{aws.String(policyName)}, - } + _, err = FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) - getResp, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, request) - if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodePolicyNotFoundException) { - log.Printf("[WARN] ELB Classic LB (%s) policy (%s) not found, removing from state", lbName, policyName) - d.SetId("") - return diags - } else if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { - log.Printf("[WARN] ELB Classic LB (%s) not found, removing from state", lbName) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "reading ELB Classic (%s) SSL Negotiation Policy: %s", lbName, err) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELB Classic SSL Negotiation Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - if len(getResp.PolicyDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "Unable to find policy %#v", getResp.PolicyDescriptions) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELB Classic SSL Negotiation Policy (%s): %s", d.Id(), err) } - d.Set("name", policyName) - d.Set("load_balancer", lbName) d.Set("lb_port", lbPort) + d.Set("load_balancer", lbName) + d.Set("name", policyName) // TODO: fix attribute // This was previously erroneously setting "attributes", however this cannot @@ -171,49 +153,60 @@ func resourceSSLNegotiationPolicyDelete(ctx context.Context, d *schema.ResourceD var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - lbName, _, policyName, err := SSLNegotiationPolicyParseID(d.Id()) + lbName, lbPort, policyName, err := SSLNegotiationPolicyParseResourceID(d.Id()) + if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting ELB Classic SSL Negotiation Policy (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } // Perversely, if we Set an empty list of PolicyNames, we detach the // policies attached to a listener, which is required to delete the // policy itself. - setLoadBalancerOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(d.Get("load_balancer").(string)), - LoadBalancerPort: aws.Int64(int64(d.Get("lb_port").(int))), - PolicyNames: []*string{}, + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), + PolicyNames: aws.StringSlice([]string{}), } - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setLoadBalancerOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "removing SSLNegotiationPolicy: %s", err) + _, err = conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic SSL Negotiation Policy (%s): %s", d.Id(), err) } - request := &elb.DeleteLoadBalancerPolicyInput{ + _, err = conn.DeleteLoadBalancerPolicyWithContext(ctx, &elb.DeleteLoadBalancerPolicyInput{ LoadBalancerName: aws.String(lbName), PolicyName: aws.String(policyName), - } + }) - if _, err := conn.DeleteLoadBalancerPolicyWithContext(ctx, request); err != nil { + if err != nil { return sdkdiag.AppendErrorf(diags, "deleting ELB Classic SSL Negotiation Policy (%s): %s", d.Id(), err) } + return diags } -// SSLNegotiationPolicyParseID takes an ID and parses it into -// it's constituent parts. You need three axes (LB name, policy name, and LB -// port) to create or identify an SSL negotiation policy in AWS's API. -func SSLNegotiationPolicyParseID(id string) (string, int, string, error) { - const partCount = 3 - parts := strings.SplitN(id, ":", partCount) - if n := len(parts); n != partCount { - return "", 0, "", fmt.Errorf("incorrect format of SSL negotiation policy resource ID. Expected %d parts, got %d", partCount, n) - } +const sslNegotiationPolicyResourceIDSeparator = ":" - port, err := strconv.Atoi(parts[1]) - if err != nil { - return "", 0, "", fmt.Errorf("parsing SSL negotiation policy resource ID port: %w", err) +func SSLNegotiationPolicyCreateResourceID(lbName string, lbPort int, policyName string) string { + parts := []string{lbName, strconv.Itoa(lbPort), policyName} + id := strings.Join(parts, sslNegotiationPolicyResourceIDSeparator) + + return id +} + +func SSLNegotiationPolicyParseResourceID(id string) (string, int, string, error) { + parts := strings.Split(id, sslNegotiationPolicyResourceIDSeparator) + + if len(parts) == 3 && parts[0] != "" && parts[1] != "" && parts[2] != "" { + v, err := strconv.Atoi(parts[1]) + + if err != nil { + return "", 0, "", err + } + + return parts[0], v, parts[2], nil } - return parts[0], port, parts[2], nil + return "", 0, "", fmt.Errorf("unexpected format for ID (%[1]s), expected LBNAME%[2]sLBPORT%[2]sPOLICYNAME", id, sslNegotiationPolicyResourceIDSeparator) } diff --git a/internal/service/elb/lb_ssl_negotiation_policy_test.go b/internal/service/elb/lb_ssl_negotiation_policy_test.go index abd402abacb..258db074346 100644 --- a/internal/service/elb/lb_ssl_negotiation_policy_test.go +++ b/internal/service/elb/lb_ssl_negotiation_policy_test.go @@ -3,11 +3,8 @@ package elb_test import ( "context" "fmt" - "log" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -15,16 +12,15 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfelb "github.com/hashicorp/terraform-provider-aws/internal/service/elb" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBSSLNegotiationPolicy_basic(t *testing.T) { ctx := acctest.Context(t) - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(8)) // ELB name cannot be longer than 32 characters - elbResourceName := "aws_elb.test" - resourceName := "aws_lb_ssl_negotiation_policy.test" - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) key := acctest.TLSRSAPrivateKeyPEM(t, 2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") + resourceName := "aws_lb_ssl_negotiation_policy.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -35,7 +31,7 @@ func TestAccELBSSLNegotiationPolicy_basic(t *testing.T) { { Config: testAccLBSSLNegotiationPolicyConfig_basic(rName, key, certificate), Check: resource.ComposeTestCheckFunc( - testAccCheckLBSSLNegotiationPolicy(ctx, elbResourceName, resourceName), + testAccCheckLBSSLNegotiationPolicy(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "attribute.#", "7"), ), }, @@ -45,13 +41,10 @@ func TestAccELBSSLNegotiationPolicy_basic(t *testing.T) { func TestAccELBSSLNegotiationPolicy_disappears(t *testing.T) { ctx := acctest.Context(t) - var loadBalancer elb.LoadBalancerDescription - rName := fmt.Sprintf("tf-acc-test-%s", sdkacctest.RandString(8)) // ELB name cannot be longer than 32 characters - elbResourceName := "aws_elb.test" - resourceName := "aws_lb_ssl_negotiation_policy.test" - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) key := acctest.TLSRSAPrivateKeyPEM(t, 2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") + resourceName := "aws_lb_ssl_negotiation_policy.test" resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -62,9 +55,8 @@ func TestAccELBSSLNegotiationPolicy_disappears(t *testing.T) { { Config: testAccLBSSLNegotiationPolicyConfig_basic(rName, key, certificate), Check: resource.ComposeTestCheckFunc( - testAccCheckLBSSLNegotiationPolicy(ctx, elbResourceName, resourceName), - testAccCheckLoadBalancerExists(ctx, elbResourceName, &loadBalancer), - testAccCheckLoadBalancerDisappears(ctx, &loadBalancer), + testAccCheckLBSSLNegotiationPolicy(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceSSLNegotiationPolicy(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -77,148 +69,68 @@ func testAccCheckLBSSLNegotiationPolicyDestroy(ctx context.Context) resource.Tes conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_elb" && rs.Type != "aws_lb_ssl_negotiation_policy" { + if rs.Type != "aws_lb_ssl_negotiation_policy" { continue } - // Check that the ELB is destroyed - if rs.Type == "aws_elb" { - describe, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, - }) - - if err == nil { - if len(describe.LoadBalancerDescriptions) != 0 && - *describe.LoadBalancerDescriptions[0].LoadBalancerName == rs.Primary.ID { - return fmt.Errorf("ELB still exists") - } - } - - // Verify the error - providerErr, ok := err.(awserr.Error) - if !ok { - return err - } - - if providerErr.Code() != "LoadBalancerNotFound" { - return fmt.Errorf("Unexpected error: %s", err) - } - } else { - // Check that the SSL Negotiation Policy is destroyed - elbName, _, policyName, err := tfelb.SSLNegotiationPolicyParseID(rs.Primary.ID) - if err != nil { - return err - } - - _, err = conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(elbName), - PolicyNames: []*string{aws.String(policyName)}, - }) - - if err == nil { - return fmt.Errorf("ELB SSL Negotiation Policy still exists") - } + lbName, lbPort, policyName, err := tfelb.SSLNegotiationPolicyParseResourceID(rs.Primary.ID) + + if err != nil { + return err + } + + _, err = tfelb.FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) + + if tfresource.NotFound(err) { + continue } + + if err != nil { + return err + } + + return fmt.Errorf("ELB Classic SSL Negotiation Policy %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckLBSSLNegotiationPolicy(ctx context.Context, elbResource string, policyResource string) resource.TestCheckFunc { +func testAccCheckLBSSLNegotiationPolicy(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[elbResource] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", elbResource) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - policy, ok := s.RootModule().Resources[policyResource] - if !ok { - return fmt.Errorf("Not found: %s", policyResource) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - - elbName, _, policyName, err := tfelb.SSLNegotiationPolicyParseID(policy.Primary.ID) - if err != nil { - return err + return fmt.Errorf("No ELB Classic SSL Negotiation Policy ID is set") } - resp, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(elbName), - PolicyNames: []*string{aws.String(policyName)}, - }) + lbName, lbPort, policyName, err := tfelb.SSLNegotiationPolicyParseResourceID(rs.Primary.ID) if err != nil { - log.Printf("[ERROR] Problem describing load balancer policy '%s': %s", policyName, err) return err } - if len(resp.PolicyDescriptions) != 1 { - return fmt.Errorf("Unable to find policy %#v", resp.PolicyDescriptions) - } - - attrmap := policyAttributesToMap(&resp.PolicyDescriptions[0].PolicyAttributeDescriptions) - if attrmap["Protocol-TLSv1"] != "false" { - return fmt.Errorf("Policy attribute 'Protocol-TLSv1' was of value %s instead of false!", attrmap["Protocol-TLSv1"]) - } - if attrmap["Protocol-TLSv1.1"] != "false" { - return fmt.Errorf("Policy attribute 'Protocol-TLSv1.1' was of value %s instead of false!", attrmap["Protocol-TLSv1.1"]) - } - if attrmap["Protocol-TLSv1.2"] != "true" { - return fmt.Errorf("Policy attribute 'Protocol-TLSv1.2' was of value %s instead of true!", attrmap["Protocol-TLSv1.2"]) - } - if attrmap["Server-Defined-Cipher-Order"] != "true" { - return fmt.Errorf("Policy attribute 'Server-Defined-Cipher-Order' was of value %s instead of true!", attrmap["Server-Defined-Cipher-Order"]) - } - if attrmap["ECDHE-RSA-AES128-GCM-SHA256"] != "true" { - return fmt.Errorf("Policy attribute 'ECDHE-RSA-AES128-GCM-SHA256' was of value %s instead of true!", attrmap["ECDHE-RSA-AES128-GCM-SHA256"]) - } - if attrmap["AES128-GCM-SHA256"] != "true" { - return fmt.Errorf("Policy attribute 'AES128-GCM-SHA256' was of value %s instead of true!", attrmap["AES128-GCM-SHA256"]) - } - if attrmap["EDH-RSA-DES-CBC3-SHA"] != "false" { - return fmt.Errorf("Policy attribute 'EDH-RSA-DES-CBC3-SHA' was of value %s instead of false!", attrmap["EDH-RSA-DES-CBC3-SHA"]) - } - - return nil - } -} + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() -func policyAttributesToMap(attributes *[]*elb.PolicyAttributeDescription) map[string]string { - attrmap := make(map[string]string) + _, err = tfelb.FindLoadBalancerListenerPolicyByThreePartKey(ctx, conn, lbName, lbPort, policyName) - for _, attrdef := range *attributes { - attrmap[*attrdef.AttributeName] = *attrdef.AttributeValue + return err } - - return attrmap } -// Sets the SSL Negotiation policy with attributes. func testAccLBSSLNegotiationPolicyConfig_basic(rName, key, certificate string) string { - return fmt.Sprintf(` -data "aws_availability_zones" "available" { - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` resource "aws_iam_server_certificate" "test" { - name = "%[1]s" + name = %[1]q certificate_body = "%[2]s" private_key = "%[3]s" } resource "aws_elb" "test" { - name = "%[1]s" + name = %[1]q availability_zones = [data.aws_availability_zones.available.names[0]] listener { @@ -231,7 +143,7 @@ resource "aws_elb" "test" { } resource "aws_lb_ssl_negotiation_policy" "test" { - name = "foo-policy" + name = %[1]q load_balancer = aws_elb.test.id lb_port = 443 @@ -270,5 +182,5 @@ resource "aws_lb_ssl_negotiation_policy" "test" { value = "false" } } -`, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key)) +`, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key))) } diff --git a/internal/service/elb/listener_policy.go b/internal/service/elb/listener_policy.go index 6b1a7b15254..6a99e05e7d8 100644 --- a/internal/service/elb/listener_policy.go +++ b/internal/service/elb/listener_policy.go @@ -3,24 +3,25 @@ package elb import ( "context" "fmt" + "log" "strconv" "strings" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" "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/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceListenerPolicy() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceListenerPolicyCreate, + CreateWithoutTimeout: resourceListenerPolicySet, ReadWithoutTimeout: resourceListenerPolicyRead, - UpdateWithoutTimeout: resourceListenerPolicyCreate, + UpdateWithoutTimeout: resourceListenerPolicySet, DeleteWithoutTimeout: resourceListenerPolicyDelete, Schema: map[string]*schema.Schema{ @@ -28,44 +29,43 @@ func ResourceListenerPolicy() *schema.Resource { Type: schema.TypeString, Required: true, }, - - "policy_names": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Set: schema.HashString, - }, - "load_balancer_port": { Type: schema.TypeInt, Required: true, }, + "policy_names": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, }, } } -func resourceListenerPolicyCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceListenerPolicySet(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName := d.Get("load_balancer_name") - - policyNames := []*string{} - if v, ok := d.GetOk("policy_names"); ok { - policyNames = flex.ExpandStringSet(v.(*schema.Set)) + lbName := d.Get("load_balancer_name").(string) + lbPort := d.Get("load_balancer_port").(int) + id := ListenerPolicyCreateResourceID(lbName, lbPort) + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), } - setOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(loadBalancerName.(string)), - LoadBalancerPort: aws.Int64(int64(d.Get("load_balancer_port").(int))), - PolicyNames: policyNames, + if v, ok := d.GetOk("policy_names"); ok && v.(*schema.Set).Len() > 0 { + input.PolicyNames = flex.ExpandStringSet(v.(*schema.Set)) } - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "setting LoadBalancerPoliciesOfListener: %s", err) + _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic Listener Policy (%s): %s", id, err) } - d.SetId(fmt.Sprintf("%s:%s", *setOpts.LoadBalancerName, strconv.FormatInt(*setOpts.LoadBalancerPort, 10))) + d.SetId(id) + return append(diags, resourceListenerPolicyRead(ctx, d, meta)...) } @@ -73,46 +73,27 @@ func resourceListenerPolicyRead(ctx context.Context, d *schema.ResourceData, met var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName, loadBalancerPort := ListenerPoliciesParseID(d.Id()) - - describeElbOpts := &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(loadBalancerName)}, - } - - describeResp, err := conn.DescribeLoadBalancersWithContext(ctx, describeElbOpts) + lbName, lbPort, err := ListenerPolicyParseResourceID(d.Id()) if err != nil { - if ec2err, ok := err.(awserr.Error); ok { - if ec2err.Code() == "LoadBalancerNotFound" { - d.SetId("") - return sdkdiag.AppendErrorf(diags, "LoadBalancerNotFound: %s", err) - } - } - return sdkdiag.AppendErrorf(diags, "retrieving ELB description: %s", err) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - if len(describeResp.LoadBalancerDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions) - } + policyNames, err := FindLoadBalancerListenerPolicyByTwoPartKey(ctx, conn, lbName, lbPort) - lb := describeResp.LoadBalancerDescriptions[0] - - policyNames := []*string{} - for _, listener := range lb.ListenerDescriptions { - if loadBalancerPort != strconv.Itoa(int(aws.Int64Value(listener.Listener.LoadBalancerPort))) { - continue - } - - policyNames = append(policyNames, listener.PolicyNames...) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELB Classic Listener Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - d.Set("load_balancer_name", loadBalancerName) - loadBalancerPortVal, err := strconv.ParseInt(loadBalancerPort, 10, 64) if err != nil { - return sdkdiag.AppendErrorf(diags, "parsing load balancer port: %s", err) + return sdkdiag.AppendErrorf(diags, "reading ELB Classic Listener Policy (%s): %s", d.Id(), err) } - d.Set("load_balancer_port", loadBalancerPortVal) - d.Set("policy_names", flex.FlattenStringList(policyNames)) + + d.Set("load_balancer_name", lbName) + d.Set("load_balancer_port", lbPort) + d.Set("policy_names", policyNames) return diags } @@ -121,27 +102,73 @@ func resourceListenerPolicyDelete(ctx context.Context, d *schema.ResourceData, m var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName, loadBalancerPort := ListenerPoliciesParseID(d.Id()) + lbName, lbPort, err := ListenerPolicyParseResourceID(d.Id()) - loadBalancerPortInt, err := strconv.ParseInt(loadBalancerPort, 10, 64) if err != nil { - return sdkdiag.AppendErrorf(diags, "parsing loadBalancerPort as integer: %s", err) + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - setOpts := &elb.SetLoadBalancerPoliciesOfListenerInput{ - LoadBalancerName: aws.String(loadBalancerName), - LoadBalancerPort: aws.Int64(loadBalancerPortInt), - PolicyNames: []*string{}, + input := &elb.SetLoadBalancerPoliciesOfListenerInput{ + LoadBalancerName: aws.String(lbName), + LoadBalancerPort: aws.Int64(int64(lbPort)), + PolicyNames: aws.StringSlice([]string{}), } - if _, err := conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, setOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "setting LoadBalancerPoliciesOfListener: %s", err) + log.Printf("[DEBUG] Deleting ELB Classic Listener Policy: %s", d.Id()) + _, err = conn.SetLoadBalancerPoliciesOfListenerWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting ELB Classic Listener Policy (%s): %s", d.Id(), err) } return diags } -func ListenerPoliciesParseID(id string) (string, string) { - parts := strings.SplitN(id, ":", 2) - return parts[0], parts[1] +func FindLoadBalancerListenerPolicyByTwoPartKey(ctx context.Context, conn *elb.ELB, lbName string, lbPort int) ([]string, error) { + lb, err := FindLoadBalancerByName(ctx, conn, lbName) + + if err != nil { + return nil, err + } + + var policyNames []string + + for _, v := range lb.ListenerDescriptions { + if v == nil { + continue + } + + if aws.Int64Value(v.Listener.LoadBalancerPort) != int64(lbPort) { + continue + } + + policyNames = append(policyNames, aws.StringValueSlice(v.PolicyNames)...) + } + + return policyNames, nil +} + +const listenerPolicyResourceIDSeparator = ":" + +func ListenerPolicyCreateResourceID(lbName string, lbPort int) string { + parts := []string{lbName, strconv.Itoa(lbPort)} + id := strings.Join(parts, listenerPolicyResourceIDSeparator) + + return id +} + +func ListenerPolicyParseResourceID(id string) (string, int, error) { + parts := strings.Split(id, listenerPolicyResourceIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + v, err := strconv.Atoi(parts[1]) + + if err != nil { + return "", 0, err + } + + return parts[0], v, nil + } + + return "", 0, fmt.Errorf("unexpected format for ID (%[1]s), expected LBNAME%[2]sLBPORT", id, listenerPolicyResourceIDSeparator) } diff --git a/internal/service/elb/listener_policy_test.go b/internal/service/elb/listener_policy_test.go index 9e49e6dbd9f..ae6feeb55e5 100644 --- a/internal/service/elb/listener_policy_test.go +++ b/internal/service/elb/listener_policy_test.go @@ -3,26 +3,23 @@ package elb_test import ( "context" "fmt" - "strings" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" - "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" tfelb "github.com/hashicorp/terraform-provider-aws/internal/service/elb" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBListenerPolicy_basic(t *testing.T) { ctx := acctest.Context(t) - rChar := sdkacctest.RandStringFromCharSet(6, sdkacctest.CharSetAlpha) - lbName := rChar - mcName := rChar + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_load_balancer_listener_policy.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), @@ -30,175 +27,102 @@ func TestAccELBListenerPolicy_basic(t *testing.T) { CheckDestroy: testAccCheckListenerPolicyDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccListenerPolicyConfig_basic0(lbName, mcName), - Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyState(ctx, "aws_elb.test-lb", "aws_load_balancer_policy.magic-cookie-sticky"), - testAccCheckListenerPolicyState(ctx, lbName, int64(80), mcName, true), - ), - }, - { - Config: testAccListenerPolicyConfig_basic1(lbName, mcName), + Config: testAccListenerPolicyConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckPolicyState(ctx, "aws_elb.test-lb", "aws_load_balancer_policy.magic-cookie-sticky"), - testAccCheckListenerPolicyState(ctx, lbName, int64(80), mcName, true), + testAccCheckListenerPolicyExists(ctx, resourceName), + resource.TestCheckResourceAttr(resourceName, "load_balancer_port", "80"), + resource.TestCheckResourceAttr(resourceName, "policy_names.#", "1"), + resource.TestCheckTypeSetElemAttrPair(resourceName, "policy_names.*", "aws_load_balancer_policy.test", "policy_name"), ), }, + }, + }) +} + +func TestAccELBListenerPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_load_balancer_listener_policy.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, elb.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckListenerPolicyDestroy(ctx), + Steps: []resource.TestStep{ { - Config: testAccListenerPolicyConfig_basic2(lbName), + Config: testAccListenerPolicyConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckListenerPolicyState(ctx, lbName, int64(80), mcName, false), + testAccCheckListenerPolicyExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceListenerPolicy(), resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) } -func policyInListenerPolicies(str string, list []string) bool { - for _, v := range list { - if v == str { - return true - } - } - return false -} - func testAccCheckListenerPolicyDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() for _, rs := range s.RootModule().Resources { - switch { - case rs.Type == "aws_load_balancer_policy": - loadBalancerName, policyName := tfelb.ListenerPoliciesParseID(rs.Primary.ID) - out, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(loadBalancerName), - PolicyNames: []*string{aws.String(policyName)}, - }) - if err != nil { - if ec2err, ok := err.(awserr.Error); ok && (ec2err.Code() == "PolicyNotFound" || ec2err.Code() == "LoadBalancerNotFound") { - continue - } - return err - } - if len(out.PolicyDescriptions) > 0 { - return fmt.Errorf("Policy still exists") - } - case rs.Type == "aws_load_listener_policy": - loadBalancerName, _ := tfelb.ListenerPoliciesParseID(rs.Primary.ID) - out, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(loadBalancerName)}, - }) - - if tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { - continue - } - - if err != nil { - return err - } - - policyNames := []string{} - for k := range rs.Primary.Attributes { - if strings.HasPrefix(k, "policy_names.") && strings.HasSuffix(k, ".name") { - value_key := fmt.Sprintf("%s.value", strings.TrimSuffix(k, ".name")) - policyNames = append(policyNames, rs.Primary.Attributes[value_key]) - } - } - for _, policyName := range policyNames { - for _, listener := range out.LoadBalancerDescriptions[0].ListenerDescriptions { - policyStrings := []string{} - for _, pol := range listener.PolicyNames { - policyStrings = append(policyStrings, *pol) - } - if policyInListenerPolicies(policyName, policyStrings) { - return fmt.Errorf("Policy still exists and is assigned") - } - } - } - default: + if rs.Type != "aws_load_balancer_listener_policy" { continue } - } - return nil - } -} -func testAccCheckListenerPolicyState(ctx context.Context, loadBalancerName string, loadBalancerListenerPort int64, loadBalancerListenerPolicyName string, assigned bool) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() + lbName, lbPort, err := tfelb.ListenerPolicyParseResourceID(rs.Primary.ID) - loadBalancerDescription, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(loadBalancerName)}, - }) - if err != nil { - return err - } + if err != nil { + return err + } - for _, listener := range loadBalancerDescription.LoadBalancerDescriptions[0].ListenerDescriptions { - if *listener.Listener.LoadBalancerPort != loadBalancerListenerPort { + _, err = tfelb.FindLoadBalancerListenerPolicyByTwoPartKey(ctx, conn, lbName, lbPort) + + if tfresource.NotFound(err) { continue } - policyStrings := []string{} - for _, pol := range listener.PolicyNames { - policyStrings = append(policyStrings, *pol) - } - if policyInListenerPolicies(loadBalancerListenerPolicyName, policyStrings) != assigned { - if assigned { - return fmt.Errorf("Policy no longer assigned %s not in %+v", loadBalancerListenerPolicyName, policyStrings) - } else { - return fmt.Errorf("Policy exists and is assigned") - } + + if err != nil { + return err } + + return fmt.Errorf("ELB Classic Listener Policy %s still exists", rs.Primary.ID) } return nil } } -func testAccListenerPolicyConfig_basic0(lbName, mcName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_elb" "test-lb" { - name = "%s" - availability_zones = [data.aws_availability_zones.available.names[0]] +func testAccCheckListenerPolicyExists(ctx context.Context, n string) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } - listener { - instance_port = 80 - instance_protocol = "http" - lb_port = 80 - lb_protocol = "http" - } + if rs.Primary.ID == "" { + return fmt.Errorf("No ELB Classic Listener Policy ID is set") + } - tags = { - Name = "tf-acc-test" - } -} + lbName, lbPort, err := tfelb.ListenerPolicyParseResourceID(rs.Primary.ID) -resource "aws_load_balancer_policy" "magic-cookie-sticky" { - load_balancer_name = aws_elb.test-lb.name - policy_name = "%s" - policy_type_name = "AppCookieStickinessPolicyType" + if err != nil { + return err + } - policy_attribute { - name = "CookieName" - value = "magic_cookie" - } -} + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() -resource "aws_load_balancer_listener_policy" "test-lb-listener-policies-80" { - load_balancer_name = aws_elb.test-lb.name - load_balancer_port = 80 + _, err = tfelb.FindLoadBalancerListenerPolicyByTwoPartKey(ctx, conn, lbName, lbPort) - policy_names = [ - aws_load_balancer_policy.magic-cookie-sticky.policy_name, - ] -} -`, lbName, mcName)) + return err + } } -func testAccListenerPolicyConfig_basic1(lbName, mcName string) string { +func testAccListenerPolicyConfig_basic(rName string) string { return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_elb" "test-lb" { - name = "%s" +resource "aws_elb" "test" { + name = %[1]q availability_zones = [data.aws_availability_zones.available.names[0]] listener { @@ -207,50 +131,26 @@ resource "aws_elb" "test-lb" { lb_port = 80 lb_protocol = "http" } - - tags = { - Name = "tf-acc-test" - } } -resource "aws_load_balancer_policy" "magic-cookie-sticky" { - load_balancer_name = aws_elb.test-lb.name - policy_name = "%s" +resource "aws_load_balancer_policy" "test" { + load_balancer_name = aws_elb.test.name + policy_name = %[1]q policy_type_name = "AppCookieStickinessPolicyType" policy_attribute { name = "CookieName" - value = "unicorn_cookie" + value = "wafer" } } -resource "aws_load_balancer_listener_policy" "test-lb-listener-policies-80" { - load_balancer_name = aws_elb.test-lb.name +resource "aws_load_balancer_listener_policy" "test" { + load_balancer_name = aws_elb.test.name load_balancer_port = 80 policy_names = [ - aws_load_balancer_policy.magic-cookie-sticky.policy_name, + aws_load_balancer_policy.test.policy_name, ] } -`, lbName, mcName)) -} - -func testAccListenerPolicyConfig_basic2(lbName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_elb" "test-lb" { - name = "%s" - availability_zones = [data.aws_availability_zones.available.names[0]] - - listener { - instance_port = 80 - instance_protocol = "http" - lb_port = 80 - lb_protocol = "http" - } - - tags = { - Name = "tf-acc-test" - } -} -`, lbName)) +`, rName)) } diff --git a/internal/service/elb/load_balancer.go b/internal/service/elb/load_balancer.go index 141f774ecd7..a4440c389c9 100644 --- a/internal/service/elb/load_balancer.go +++ b/internal/service/elb/load_balancer.go @@ -43,131 +43,126 @@ func ResourceLoadBalancer() *schema.Resource { CustomizeDiff: verify.SetTagsDiff, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, - ConflictsWith: []string{"name_prefix"}, - ValidateFunc: ValidName, - }, - "name_prefix": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"name"}, - ValidateFunc: validNamePrefix, + "access_logs": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bucket": { + Type: schema.TypeString, + Required: true, + }, + "bucket_prefix": { + Type: schema.TypeString, + Optional: true, + }, + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "interval": { + Type: schema.TypeInt, + Optional: true, + Default: 60, + ValidateFunc: ValidAccessLogsInterval, + }, + }, + }, }, - "arn": { Type: schema.TypeString, Computed: true, }, - - "internal": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Computed: true, - }, - - "cross_zone_load_balancing": { - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "availability_zones": { Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, Optional: true, Computed: true, - Set: schema.HashString, - }, - - "instances": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Computed: true, - Set: schema.HashString, - }, - - "security_groups": { - Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Computed: true, - Set: schema.HashString, }, - - "source_security_group": { - Type: schema.TypeString, - Optional: true, - Computed: true, - }, - - "source_security_group_id": { - Type: schema.TypeString, - Computed: true, - }, - - "subnets": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - Computed: true, - Set: schema.HashString, - }, - - "idle_timeout": { - Type: schema.TypeInt, - Optional: true, - Default: 60, - ValidateFunc: validation.IntBetween(1, 4000), - }, - "connection_draining": { Type: schema.TypeBool, Optional: true, Default: false, }, - "connection_draining_timeout": { Type: schema.TypeInt, Optional: true, Default: 300, }, - - "access_logs": { + "cross_zone_load_balancing": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "desync_mitigation_mode": { + Type: schema.TypeString, + Optional: true, + Default: "defensive", + ValidateFunc: validation.StringInSlice([]string{ + "monitor", + "defensive", + "strictest", + }, false), + }, + "dns_name": { + Type: schema.TypeString, + Computed: true, + }, + "health_check": { Type: schema.TypeList, Optional: true, + Computed: true, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "healthy_threshold": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(2, 10), + }, "interval": { Type: schema.TypeInt, - Optional: true, - Default: 60, - ValidateFunc: ValidAccessLogsInterval, + Required: true, + ValidateFunc: validation.IntBetween(5, 300), }, - "bucket": { - Type: schema.TypeString, - Required: true, + "target": { + Type: schema.TypeString, + Required: true, + ValidateFunc: ValidHeathCheckTarget, }, - "bucket_prefix": { - Type: schema.TypeString, - Optional: true, + "timeout": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(2, 60), }, - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: true, + "unhealthy_threshold": { + Type: schema.TypeInt, + Required: true, + ValidateFunc: validation.IntBetween(2, 10), }, }, }, }, - + "idle_timeout": { + Type: schema.TypeInt, + Optional: true, + Default: 60, + ValidateFunc: validation.IntBetween(1, 4000), + }, + "instances": { + Type: schema.TypeSet, + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "internal": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + Computed: true, + }, "listener": { Type: schema.TypeSet, Required: true, @@ -178,25 +173,21 @@ func ResourceLoadBalancer() *schema.Resource { Required: true, ValidateFunc: validation.IntBetween(1, 65535), }, - "instance_protocol": { Type: schema.TypeString, Required: true, ValidateFunc: validateListenerProtocol(), }, - "lb_port": { Type: schema.TypeInt, Required: true, ValidateFunc: validation.IntBetween(1, 65535), }, - "lb_protocol": { Type: schema.TypeString, Required: true, ValidateFunc: validateListenerProtocol(), }, - "ssl_certificate_id": { Type: schema.TypeString, Optional: true, @@ -206,77 +197,55 @@ func ResourceLoadBalancer() *schema.Resource { }, Set: ListenerHash, }, - - "health_check": { - Type: schema.TypeList, + "name": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + ConflictsWith: []string{"name_prefix"}, + ValidateFunc: ValidName, + }, + "name_prefix": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ConflictsWith: []string{"name"}, + ValidateFunc: validNamePrefix, + }, + "security_groups": { + Type: schema.TypeSet, Optional: true, Computed: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "healthy_threshold": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(2, 10), - }, - - "unhealthy_threshold": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(2, 10), - }, - - "target": { - Type: schema.TypeString, - Required: true, - ValidateFunc: ValidHeathCheckTarget, - }, - - "interval": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(5, 300), - }, - - "timeout": { - Type: schema.TypeInt, - Required: true, - ValidateFunc: validation.IntBetween(2, 60), - }, - }, - }, + Elem: &schema.Schema{Type: schema.TypeString}, }, - - "dns_name": { + "source_security_group": { Type: schema.TypeString, + Optional: true, Computed: true, }, - - "zone_id": { + "source_security_group_id": { Type: schema.TypeString, Computed: true, }, - - "desync_mitigation_mode": { - Type: schema.TypeString, + "subnets": { + Type: schema.TypeSet, Optional: true, - Default: "defensive", - ValidateFunc: validation.StringInSlice([]string{ - "monitor", - "defensive", - "strictest", - }, false), + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), + "zone_id": { + Type: schema.TypeString, + Computed: true, + }, }, } } func resourceLoadBalancerCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - elbconn := meta.(*conns.AWSClient).ELBConn() + conn := meta.(*conns.AWSClient).ELBConn() defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) @@ -323,22 +292,10 @@ func resourceLoadBalancerCreate(ctx context.Context, d *schema.ResourceData, met elbOpts.Subnets = flex.ExpandStringSet(v.(*schema.Set)) } - err = resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { - _, err := elbconn.CreateLoadBalancerWithContext(ctx, elbOpts) - - if tfawserr.ErrCodeEquals(err, elb.ErrCodeCertificateNotFoundException) { - return resource.RetryableError(fmt.Errorf("creating ELB Listener with SSL Cert, retrying: %w", err)) - } + _, err = tfresource.RetryWhenAWSErrCodeEquals(ctx, 5*time.Minute, func() (interface{}, error) { + return conn.CreateLoadBalancerWithContext(ctx, elbOpts) + }, elb.ErrCodeCertificateNotFoundException) - if err != nil { - return resource.NonRetryableError(err) - } - - return nil - }) - if tfresource.TimedOut(err) { - _, err = elbconn.CreateLoadBalancerWithContext(ctx, elbOpts) - } if err != nil { return sdkdiag.AppendErrorf(diags, "creating ELB Classic Load Balancer (%s): %s", elbName, err) } @@ -352,11 +309,21 @@ func resourceLoadBalancerCreate(ctx context.Context, d *schema.ResourceData, met func resourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - elbconn := meta.(*conns.AWSClient).ELBConn() + conn := meta.(*conns.AWSClient).ELBConn() defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - elbName := d.Id() + lb, err := FindLoadBalancerByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELB Classic Load Balancer (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELB Classic Load Balancer (%s): %s", d.Id(), err) + } arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, @@ -367,26 +334,7 @@ func resourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, meta } d.Set("arn", arn.String()) - // Retrieve the ELB properties for updating the state - describeElbOpts := &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(elbName)}, - } - - describeResp, err := elbconn.DescribeLoadBalancersWithContext(ctx, describeElbOpts) - if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { - log.Printf("[WARN] ELB Classic LB (%s) not found, removing from state", elbName) - d.SetId("") - return diags - } - - return sdkdiag.AppendErrorf(diags, "retrieving ELB Classic LB (%s): %s", elbName, err) - } - if len(describeResp.LoadBalancerDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "Unable to find ELB: %#v", describeResp.LoadBalancerDescriptions) - } - - if err := flattenLoadBalancerResource(ctx, d, meta.(*conns.AWSClient).EC2Conn(), elbconn, describeResp.LoadBalancerDescriptions[0], ignoreTagsConfig, defaultTagsConfig); err != nil { + if err := flattenLoadBalancerResource(ctx, d, meta.(*conns.AWSClient).EC2Conn(), conn, lb, ignoreTagsConfig, defaultTagsConfig); err != nil { return sdkdiag.AppendFromErr(diags, err) } return diags @@ -502,7 +450,7 @@ func flattenLoadBalancerResource(ctx context.Context, d *schema.ResourceData, ec func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - elbconn := meta.(*conns.AWSClient).ELBConn() + conn := meta.(*conns.AWSClient).ELBConn() if d.HasChange("listener") { o, n := d.GetChange("listener") @@ -527,7 +475,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met } log.Printf("[DEBUG] ELB Delete Listeners opts: %s", deleteListenersOpts) - _, err := elbconn.DeleteLoadBalancerListenersWithContext(ctx, deleteListenersOpts) + _, err := conn.DeleteLoadBalancerListenersWithContext(ctx, deleteListenersOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure removing outdated ELB listeners: %s", err) } @@ -542,7 +490,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met // Occasionally AWS will error with a 'duplicate listener', without any // other listeners on the ELB. Retry here to eliminate that. err := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { - _, err := elbconn.CreateLoadBalancerListenersWithContext(ctx, input) + _, err := conn.CreateLoadBalancerListenersWithContext(ctx, input) if err != nil { if tfawserr.ErrCodeEquals(err, elb.ErrCodeDuplicateListenerException) { log.Printf("[DEBUG] Duplicate listener found for ELB (%s), retrying", d.Id()) @@ -560,7 +508,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met return nil }) if tfresource.TimedOut(err) { - _, err = elbconn.CreateLoadBalancerListenersWithContext(ctx, input) + _, err = conn.CreateLoadBalancerListenersWithContext(ctx, input) } if err != nil { return sdkdiag.AppendErrorf(diags, "Failure adding new or updated ELB listeners: %s", err) @@ -584,7 +532,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met Instances: add, } - _, err := elbconn.RegisterInstancesWithLoadBalancerWithContext(ctx, ®isterInstancesOpts) + _, err := conn.RegisterInstancesWithLoadBalancerWithContext(ctx, ®isterInstancesOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure registering instances with ELB: %s", err) } @@ -595,7 +543,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met Instances: remove, } - _, err := elbconn.DeregisterInstancesFromLoadBalancerWithContext(ctx, &deRegisterInstancesOpts) + _, err := conn.DeregisterInstancesFromLoadBalancerWithContext(ctx, &deRegisterInstancesOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure deregistering instances from ELB: %s", err) } @@ -638,7 +586,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met } log.Printf("[DEBUG] ELB Modify Load Balancer Attributes Request: %#v", attrs) - _, err := elbconn.ModifyLoadBalancerAttributesWithContext(ctx, &attrs) + _, err := conn.ModifyLoadBalancerAttributesWithContext(ctx, &attrs) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure configuring ELB attributes: %s", err) } @@ -662,7 +610,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met }, } - _, err := elbconn.ModifyLoadBalancerAttributesWithContext(ctx, &attrs) + _, err := conn.ModifyLoadBalancerAttributesWithContext(ctx, &attrs) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure configuring ELB attributes: %s", err) } @@ -680,7 +628,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met }, } - _, err := elbconn.ModifyLoadBalancerAttributesWithContext(ctx, &attrs) + _, err := conn.ModifyLoadBalancerAttributesWithContext(ctx, &attrs) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure configuring ELB attributes: %s", err) } @@ -700,7 +648,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met Timeout: aws.Int64(int64(check["timeout"].(int))), }, } - _, err := elbconn.ConfigureHealthCheckWithContext(ctx, &configureHealthCheckOpts) + _, err := conn.ConfigureHealthCheckWithContext(ctx, &configureHealthCheckOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure configuring health check for ELB: %s", err) } @@ -713,7 +661,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met SecurityGroups: flex.ExpandStringSet(d.Get("security_groups").(*schema.Set)), } - _, err := elbconn.ApplySecurityGroupsToLoadBalancerWithContext(ctx, &applySecurityGroupsOpts) + _, err := conn.ApplySecurityGroupsToLoadBalancerWithContext(ctx, &applySecurityGroupsOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure applying security groups to ELB: %s", err) } @@ -734,7 +682,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met } log.Printf("[DEBUG] ELB enable availability zones opts: %s", enableOpts) - _, err := elbconn.EnableAvailabilityZonesForLoadBalancerWithContext(ctx, enableOpts) + _, err := conn.EnableAvailabilityZonesForLoadBalancerWithContext(ctx, enableOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure enabling ELB availability zones: %s", err) } @@ -747,7 +695,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met } log.Printf("[DEBUG] ELB disable availability zones opts: %s", disableOpts) - _, err := elbconn.DisableAvailabilityZonesForLoadBalancerWithContext(ctx, disableOpts) + _, err := conn.DisableAvailabilityZonesForLoadBalancerWithContext(ctx, disableOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure disabling ELB availability zones: %s", err) } @@ -769,7 +717,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met } log.Printf("[DEBUG] ELB detach subnets opts: %s", detachOpts) - _, err := elbconn.DetachLoadBalancerFromSubnetsWithContext(ctx, detachOpts) + _, err := conn.DetachLoadBalancerFromSubnetsWithContext(ctx, detachOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "Failure removing ELB subnets: %s", err) } @@ -783,7 +731,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met log.Printf("[DEBUG] ELB attach subnets opts: %s", attachOpts) err := resource.RetryContext(ctx, 5*time.Minute, func() *resource.RetryError { - _, err := elbconn.AttachLoadBalancerToSubnetsWithContext(ctx, attachOpts) + _, err := conn.AttachLoadBalancerToSubnetsWithContext(ctx, attachOpts) if err != nil { if tfawserr.ErrMessageContains(err, elb.ErrCodeInvalidConfigurationRequestException, "cannot be attached to multiple subnets in the same AZ") { // eventually consistent issue with removing a subnet in AZ1 and @@ -796,7 +744,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met return nil }) if tfresource.TimedOut(err) { - _, err = elbconn.AttachLoadBalancerToSubnetsWithContext(ctx, attachOpts) + _, err = conn.AttachLoadBalancerToSubnetsWithContext(ctx, attachOpts) } if err != nil { return sdkdiag.AppendErrorf(diags, "Failure adding ELB subnets: %s", err) @@ -807,7 +755,7 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met if d.HasChange("tags_all") { o, n := d.GetChange("tags_all") - if err := UpdateTags(ctx, elbconn, d.Id(), o, n); err != nil { + if err := UpdateTags(ctx, conn, d.Id(), o, n); err != nil { return sdkdiag.AppendErrorf(diags, "updating ELB(%s) tags: %s", d.Id(), err) } } @@ -817,28 +765,62 @@ func resourceLoadBalancerUpdate(ctx context.Context, d *schema.ResourceData, met func resourceLoadBalancerDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - elbconn := meta.(*conns.AWSClient).ELBConn() - - log.Printf("[INFO] Deleting ELB: %s", d.Id()) + conn := meta.(*conns.AWSClient).ELBConn() - // Destroy the load balancer - deleteElbOpts := elb.DeleteLoadBalancerInput{ + log.Printf("[INFO] Deleting ELB Classic Load Balancer: %s", d.Id()) + _, err := conn.DeleteLoadBalancerWithContext(ctx, &elb.DeleteLoadBalancerInput{ LoadBalancerName: aws.String(d.Id()), - } - if _, err := elbconn.DeleteLoadBalancerWithContext(ctx, &deleteElbOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting ELB: %s", err) + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ELB Classic Load Balancer (%s): %s", d.Id(), err) } - name := d.Get("name").(string) + err = cleanupNetworkInterfaces(ctx, meta.(*conns.AWSClient).EC2Conn(), d.Id()) - err := CleanupNetworkInterfaces(ctx, meta.(*conns.AWSClient).EC2Conn(), name) if err != nil { - log.Printf("[WARN] Failed to cleanup ENIs for ELB %q: %#v", name, err) + diags = sdkdiag.AppendWarningf(diags, "cleaning up ELB Classic Load Balancer (%s) ENIs: %s", d.Id(), err) } return diags } +func FindLoadBalancerByName(ctx context.Context, conn *elb.ELB, name string) (*elb.LoadBalancerDescription, error) { + input := &elb.DescribeLoadBalancersInput{ + LoadBalancerNames: aws.StringSlice([]string{name}), + } + + output, err := conn.DescribeLoadBalancersWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, elb.ErrCodeAccessPointNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.LoadBalancerDescriptions) == 0 || output.LoadBalancerDescriptions[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.LoadBalancerDescriptions); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + // Eventual consistency check. + if aws.StringValue(output.LoadBalancerDescriptions[0].LoadBalancerName) != name { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return output.LoadBalancerDescriptions[0], nil +} + func ListenerHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) @@ -963,7 +945,7 @@ func validateListenerProtocol() schema.SchemaValidateFunc { // but the cleanup is asynchronous and may take time // which then blocks IGW, SG or VPC on deletion // So we make the cleanup "synchronous" here -func CleanupNetworkInterfaces(ctx context.Context, conn *ec2.EC2, name string) error { +func cleanupNetworkInterfaces(ctx context.Context, conn *ec2.EC2, name string) error { // https://aws.amazon.com/premiumsupport/knowledge-center/elb-find-load-balancer-IP/. networkInterfaces, err := tfec2.FindNetworkInterfacesByAttachmentInstanceOwnerIDAndDescription(ctx, conn, "amazon-elb", "ELB "+name) diff --git a/internal/service/elb/load_balancer_data_source.go b/internal/service/elb/load_balancer_data_source.go index 8aef38e9ca4..11e9c10297d 100644 --- a/internal/service/elb/load_balancer_data_source.go +++ b/internal/service/elb/load_balancer_data_source.go @@ -3,7 +3,6 @@ package elb import ( "context" "fmt" - "log" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" @@ -60,7 +59,6 @@ func DataSourceLoadBalancer() *schema.Resource { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, "connection_draining": { @@ -125,7 +123,6 @@ func DataSourceLoadBalancer() *schema.Resource { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, "internal": { @@ -171,7 +168,6 @@ func DataSourceLoadBalancer() *schema.Resource { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, "source_security_group": { @@ -188,7 +184,6 @@ func DataSourceLoadBalancer() *schema.Resource { Type: schema.TypeSet, Elem: &schema.Schema{Type: schema.TypeString}, Computed: true, - Set: schema.HashString, }, "desync_mitigation_mode": { @@ -209,46 +204,38 @@ func DataSourceLoadBalancer() *schema.Resource { func dataSourceLoadBalancerRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() + ec2conn := meta.(*conns.AWSClient).EC2Conn() ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig lbName := d.Get("name").(string) + lb, err := FindLoadBalancerByName(ctx, conn, lbName) - input := &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: aws.StringSlice([]string{lbName}), + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELB Classic Load Balancer (%s): %s", lbName, err) } - log.Printf("[DEBUG] Reading ELB: %s", input) - resp, err := conn.DescribeLoadBalancersWithContext(ctx, input) - if err != nil { - return sdkdiag.AppendErrorf(diags, "retrieving LB: %s", err) + d.SetId(aws.StringValue(lb.LoadBalancerName)) + + input := &elb.DescribeLoadBalancerAttributesInput{ + LoadBalancerName: aws.String(d.Id()), } - if len(resp.LoadBalancerDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "search returned %d results, please revise so only one is returned", len(resp.LoadBalancerDescriptions)) + + output, err := conn.DescribeLoadBalancerAttributesWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ELB Classic Load Balancer (%s) attributes: %s", d.Id(), err) } - d.SetId(aws.StringValue(resp.LoadBalancerDescriptions[0].LoadBalancerName)) + + lbAttrs := output.LoadBalancerAttributes arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Region: meta.(*conns.AWSClient).Region, Service: "elasticloadbalancing", AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("loadbalancer/%s", aws.StringValue(resp.LoadBalancerDescriptions[0].LoadBalancerName)), + Resource: fmt.Sprintf("loadbalancer/%s", d.Id()), } d.Set("arn", arn.String()) - - lb := resp.LoadBalancerDescriptions[0] - ec2conn := meta.(*conns.AWSClient).EC2Conn() - - describeAttrsOpts := &elb.DescribeLoadBalancerAttributesInput{ - LoadBalancerName: aws.String(d.Id()), - } - describeAttrsResp, err := conn.DescribeLoadBalancerAttributesWithContext(ctx, describeAttrsOpts) - if err != nil { - return sdkdiag.AppendErrorf(diags, "retrieving ELB: %s", err) - } - - lbAttrs := describeAttrsResp.LoadBalancerAttributes - d.Set("name", lb.LoadBalancerName) d.Set("dns_name", lb.DNSName) d.Set("zone_id", lb.CanonicalHostedZoneNameID) diff --git a/internal/service/elb/load_balancer_test.go b/internal/service/elb/load_balancer_test.go index 66f059d3a49..bdad5b03072 100644 --- a/internal/service/elb/load_balancer_test.go +++ b/internal/service/elb/load_balancer_test.go @@ -10,7 +10,6 @@ import ( // nosemgrep:ci.aws-sdk-go-multiple-service-imports "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -18,6 +17,7 @@ import ( // nosemgrep:ci.aws-sdk-go-multiple-service-imports "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfelb "github.com/hashicorp/terraform-provider-aws/internal/service/elb" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBLoadBalancer_basic(t *testing.T) { @@ -74,7 +74,7 @@ func TestAccELBLoadBalancer_disappears(t *testing.T) { Config: testAccLoadBalancerConfig_basic, Check: resource.ComposeTestCheckFunc( testAccCheckLoadBalancerExists(ctx, resourceName, &loadBalancer), - testAccCheckLoadBalancerDisappears(ctx, &loadBalancer), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourceLoadBalancer(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -358,7 +358,7 @@ func TestAccELBLoadBalancer_ListenerSSLCertificateID_iamServerCertificate(t *tes Steps: []resource.TestStep{ { Config: testAccLoadBalancerConfig_listenerIAMServerCertificate(rName, certificate, key, "tcp"), - ExpectError: regexp.MustCompile(`ssl_certificate_id may be set only when protocol is 'https' or 'ssl'`), + ExpectError: regexp.MustCompile(`"ssl_certificate_id" may be set only when "protocol" is "https" or "ssl"`), }, { Config: testAccLoadBalancerConfig_listenerIAMServerCertificate(rName, certificate, key, "https"), @@ -369,7 +369,7 @@ func TestAccELBLoadBalancer_ListenerSSLCertificateID_iamServerCertificate(t *tes }, { Config: testAccLoadBalancerConfig_listenerIAMServerCertificateAddInvalidListener(rName, certificate, key), - ExpectError: regexp.MustCompile(`ssl_certificate_id may be set only when protocol is 'https' or 'ssl'`), + ExpectError: regexp.MustCompile(`"ssl_certificate_id" may be set only when "protocol" is "https" or "ssl"`), }, }, }) @@ -983,42 +983,45 @@ func testAccCheckLoadBalancerDestroy(ctx context.Context) resource.TestCheckFunc continue } - describe, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, - }) + _, err := tfelb.FindLoadBalancerByName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(describe.LoadBalancerDescriptions) != 0 && - *describe.LoadBalancerDescriptions[0].LoadBalancerName == rs.Primary.ID { - return fmt.Errorf("ELB still exists") - } + if tfresource.NotFound(err) { + continue } - // Verify the error - providerErr, ok := err.(awserr.Error) - if !ok { + if err != nil { return err } - if providerErr.Code() != elb.ErrCodeAccessPointNotFoundException { - return fmt.Errorf("Unexpected error: %s", err) - } + return fmt.Errorf("ELB Classic Load Balancer %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckLoadBalancerDisappears(ctx context.Context, loadBalancer *elb.LoadBalancerDescription) resource.TestCheckFunc { +func testAccCheckLoadBalancerExists(ctx context.Context, n string, v *elb.LoadBalancerDescription) 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 ELB Classic Load Balancer ID is set") + } + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - input := elb.DeleteLoadBalancerInput{ - LoadBalancerName: loadBalancer.LoadBalancerName, + output, err := tfelb.FindLoadBalancerByName(ctx, conn, rs.Primary.ID) + + if err != nil { + return err } - _, err := conn.DeleteLoadBalancerWithContext(ctx, &input) - return err + *v = *output + + return nil } } @@ -1046,47 +1049,6 @@ func testAccCheckLoadBalancerAttributes(conf *elb.LoadBalancerDescription) resou } } -func testAccCheckLoadBalancerExists(ctx context.Context, n string, res *elb.LoadBalancerDescription) 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 ELB ID is set") - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - - describe, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, - }) - - if err != nil { - return err - } - - if len(describe.LoadBalancerDescriptions) != 1 || - *describe.LoadBalancerDescriptions[0].LoadBalancerName != rs.Primary.ID { - return fmt.Errorf("ELB not found") - } - - *res = *describe.LoadBalancerDescriptions[0] - - // Confirm source_security_group_id for ELBs in a VPC - // See https://github.com/hashicorp/terraform/pull/3780 - if res.VPCId != nil { - sgid := rs.Primary.Attributes["source_security_group_id"] - if sgid == "" { - return fmt.Errorf("Expected to find source_security_group_id for ELB, but was empty") - } - } - - return nil - } -} - const testAccLoadBalancerConfig_basic = ` data "aws_availability_zones" "available" { state = "available" diff --git a/internal/service/elb/policy.go b/internal/service/elb/policy.go index 2fb6b613aa8..15e654204f3 100644 --- a/internal/service/elb/policy.go +++ b/internal/service/elb/policy.go @@ -13,6 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourcePolicy() *schema.Resource { @@ -28,19 +29,6 @@ func ResourcePolicy() *schema.Resource { Required: true, ForceNew: true, }, - - "policy_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "policy_type_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "policy_attribute": { Type: schema.TypeSet, Optional: true, @@ -54,7 +42,6 @@ func ResourcePolicy() *schema.Resource { Type: schema.TypeString, Optional: true, }, - "value": { Type: schema.TypeString, Optional: true, @@ -66,6 +53,16 @@ func ResourcePolicy() *schema.Resource { // differences caused by additional attributes returned by the API are suppressed. DiffSuppressFunc: suppressPolicyAttributeDiffs, }, + "policy_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, + "policy_type_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, }, } } @@ -74,23 +71,27 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, meta inte var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - lbspOpts := &elb.CreateLoadBalancerPolicyInput{ - LoadBalancerName: aws.String(d.Get("load_balancer_name").(string)), - PolicyName: aws.String(d.Get("policy_name").(string)), + lbName := d.Get("load_balancer_name").(string) + policyName := d.Get("policy_name").(string) + id := PolicyCreateResourceID(lbName, policyName) + input := &elb.CreateLoadBalancerPolicyInput{ + LoadBalancerName: aws.String(lbName), + PolicyName: aws.String(policyName), PolicyTypeName: aws.String(d.Get("policy_type_name").(string)), } if v, ok := d.GetOk("policy_attribute"); ok && v.(*schema.Set).Len() > 0 { - lbspOpts.PolicyAttributes = ExpandPolicyAttributes(v.(*schema.Set).List()) + input.PolicyAttributes = ExpandPolicyAttributes(v.(*schema.Set).List()) } - if _, err := conn.CreateLoadBalancerPolicyWithContext(ctx, lbspOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "creating LoadBalancerPolicy: %s", err) + _, err := conn.CreateLoadBalancerPolicyWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "creating ELB Classic Load Balancer Policy (%s): %s", id, err) } - d.SetId(fmt.Sprintf("%s:%s", - *lbspOpts.LoadBalancerName, - *lbspOpts.PolicyName)) + d.SetId(id) + return append(diags, resourcePolicyRead(ctx, d, meta)...) } @@ -98,44 +99,30 @@ func resourcePolicyRead(ctx context.Context, d *schema.ResourceData, meta interf var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName, policyName := PolicyParseID(d.Id()) + lbName, policyName, err := PolicyParseResourceID(d.Id()) - request := &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(loadBalancerName), - PolicyNames: []*string{aws.String(policyName)}, + if err != nil { + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) } - getResp, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, request) + policy, err := FindLoadBalancerPolicyByTwoPartKey(ctx, conn, lbName, policyName) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, "LoadBalancerNotFound") { - log.Printf("[WARN] Load Balancer (%s) not found, removing from state", loadBalancerName) - d.SetId("") - return diags - } - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, elb.ErrCodePolicyNotFoundException) { - log.Printf("[WARN] Load Balancer Policy (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ELB Classic Load Balancer Policy (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "retrieving policy: %s", err) + return sdkdiag.AppendErrorf(diags, "reading ELB Classic Load Balancer Policy (%s): %s", d.Id(), err) } - if len(getResp.PolicyDescriptions) != 1 { - return sdkdiag.AppendErrorf(diags, "Unable to find policy %#v", getResp.PolicyDescriptions) - } - - policyDesc := getResp.PolicyDescriptions[0] - policyTypeName := policyDesc.PolicyTypeName - policyAttributes := policyDesc.PolicyAttributeDescriptions - - d.Set("policy_name", policyName) - d.Set("policy_type_name", policyTypeName) - d.Set("load_balancer_name", loadBalancerName) - if err := d.Set("policy_attribute", FlattenPolicyAttributes(policyAttributes)); err != nil { + d.Set("load_balancer_name", lbName) + if err := d.Set("policy_attribute", FlattenPolicyAttributes(policy.PolicyAttributeDescriptions)); err != nil { return sdkdiag.AppendErrorf(diags, "setting policy_attribute: %s", err) } + d.Set("policy_name", policyName) + d.Set("policy_type_name", policy.PolicyTypeName) return diags } @@ -145,22 +132,26 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, meta inte conn := meta.(*conns.AWSClient).ELBConn() reassignments := Reassignment{} - loadBalancerName, policyName := PolicyParseID(d.Id()) + lbName, policyName, err := PolicyParseResourceID(d.Id()) - assigned, err := resourcePolicyAssigned(ctx, policyName, loadBalancerName, conn) + if err != nil { + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) + } + + assigned, err := resourcePolicyAssigned(ctx, policyName, lbName, conn) if err != nil { return sdkdiag.AppendErrorf(diags, "determining assignment status of Load Balancer Policy %s: %s", policyName, err) } if assigned { - reassignments, err = resourcePolicyUnassign(ctx, policyName, loadBalancerName, conn) + reassignments, err = resourcePolicyUnassign(ctx, policyName, lbName, conn) if err != nil { return sdkdiag.AppendErrorf(diags, "unassigning Load Balancer Policy %s: %s", policyName, err) } } request := &elb.DeleteLoadBalancerPolicyInput{ - LoadBalancerName: aws.String(loadBalancerName), + LoadBalancerName: aws.String(lbName), PolicyName: aws.String(policyName), } @@ -192,37 +183,36 @@ func resourcePolicyDelete(ctx context.Context, d *schema.ResourceData, meta inte var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ELBConn() - loadBalancerName, policyName := PolicyParseID(d.Id()) + lbName, policyName, err := PolicyParseResourceID(d.Id()) - assigned, err := resourcePolicyAssigned(ctx, policyName, loadBalancerName, conn) + if err != nil { + return sdkdiag.AppendErrorf(diags, "parsing resource ID: %s", err) + } + + assigned, err := resourcePolicyAssigned(ctx, policyName, lbName, conn) if err != nil { return sdkdiag.AppendErrorf(diags, "determining assignment status of Load Balancer Policy %s: %s", policyName, err) } if assigned { - _, err := resourcePolicyUnassign(ctx, policyName, loadBalancerName, conn) + _, err := resourcePolicyUnassign(ctx, policyName, lbName, conn) if err != nil { return sdkdiag.AppendErrorf(diags, "unassigning Load Balancer Policy %s: %s", policyName, err) } } request := &elb.DeleteLoadBalancerPolicyInput{ - LoadBalancerName: aws.String(loadBalancerName), + LoadBalancerName: aws.String(lbName), PolicyName: aws.String(policyName), } if _, err := conn.DeleteLoadBalancerPolicyWithContext(ctx, request); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Load Balancer Policy %s: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting ELB Classic Load Balancer Policy (%s): %s", d.Id(), err) } return diags } -func PolicyParseID(id string) (string, string) { - parts := strings.SplitN(id, ":", 2) - return parts[0], parts[1] -} - func resourcePolicyAssigned(ctx context.Context, policyName, loadBalancerName string, conn *elb.ELB) (bool, error) { describeElbOpts := &elb.DescribeLoadBalancersInput{ LoadBalancerNames: []*string{aws.String(loadBalancerName)}, @@ -376,3 +366,22 @@ func suppressPolicyAttributeDiffs(k, old, new string, d *schema.ResourceData) bo // Suppress differences if the attributes returned from the API contain those configured return oldAttributes.Intersection(newAttributes).Len() == newAttributes.Len() } + +const policyResourceIDSeparator = ":" + +func PolicyCreateResourceID(lbName, policyName string) string { + parts := []string{lbName, policyName} + id := strings.Join(parts, policyResourceIDSeparator) + + return id +} + +func PolicyParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, backendServerPolicyResourceIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected LBNAME%[2]sPOLICYNAME", id, policyResourceIDSeparator) +} diff --git a/internal/service/elb/policy_test.go b/internal/service/elb/policy_test.go index 0a985d47db1..cb24f4b9b54 100644 --- a/internal/service/elb/policy_test.go +++ b/internal/service/elb/policy_test.go @@ -4,12 +4,8 @@ import ( "context" "fmt" "regexp" - "strconv" - "strings" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/elb" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -17,12 +13,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfelb "github.com/hashicorp/terraform-provider-aws/internal/service/elb" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccELBPolicy_basic(t *testing.T) { ctx := acctest.Context(t) var policy elb.PolicyDescription - loadBalancerResourceName := "aws_elb.test-lb" resourceName := "aws_load_balancer_policy.test-policy" rInt := sdkacctest.RandInt() @@ -36,7 +32,6 @@ func TestAccELBPolicy_basic(t *testing.T) { Config: testAccPolicyConfig_basic(rInt), Check: resource.ComposeTestCheckFunc( testAccCheckPolicyExists(ctx, resourceName, &policy), - testAccCheckPolicyState(ctx, loadBalancerResourceName, resourceName), ), }, }, @@ -45,9 +40,7 @@ func TestAccELBPolicy_basic(t *testing.T) { func TestAccELBPolicy_disappears(t *testing.T) { ctx := acctest.Context(t) - var loadBalancer elb.LoadBalancerDescription var policy elb.PolicyDescription - loadBalancerResourceName := "aws_elb.test-lb" resourceName := "aws_load_balancer_policy.test-policy" rInt := sdkacctest.RandInt() @@ -60,18 +53,8 @@ func TestAccELBPolicy_disappears(t *testing.T) { { Config: testAccPolicyConfig_basic(rInt), Check: resource.ComposeTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, loadBalancerResourceName, &loadBalancer), testAccCheckPolicyExists(ctx, resourceName, &policy), - testAccCheckPolicyDisappears(ctx, &loadBalancer, &policy), - ), - ExpectNonEmptyPlan: true, - }, - { - Config: testAccPolicyConfig_basic(rInt), - Check: resource.ComposeTestCheckFunc( - testAccCheckLoadBalancerExists(ctx, loadBalancerResourceName, &loadBalancer), - testAccCheckPolicyExists(ctx, resourceName, &policy), - testAccCheckLoadBalancerDisappears(ctx, &loadBalancer), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfelb.ResourcePolicy(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -223,7 +206,6 @@ func TestAccELBPolicy_SSLSecurityPolicy_predefined(t *testing.T) { func TestAccELBPolicy_updateWhileAssigned(t *testing.T) { ctx := acctest.Context(t) var policy elb.PolicyDescription - loadBalancerResourceName := "aws_elb.test-lb" resourceName := "aws_load_balancer_policy.test-policy" rInt := sdkacctest.RandInt() @@ -237,51 +219,44 @@ func TestAccELBPolicy_updateWhileAssigned(t *testing.T) { Config: testAccPolicyConfig_updateWhileAssigned0(rInt), Check: resource.ComposeTestCheckFunc( testAccCheckPolicyExists(ctx, resourceName, &policy), - testAccCheckPolicyState(ctx, loadBalancerResourceName, resourceName), ), }, { Config: testAccPolicyConfig_updateWhileAssigned1(rInt), Check: resource.ComposeTestCheckFunc( testAccCheckPolicyExists(ctx, resourceName, &policy), - testAccCheckPolicyState(ctx, loadBalancerResourceName, resourceName), ), }, }, }) } -func testAccCheckPolicyExists(ctx context.Context, resourceName string, policyDescription *elb.PolicyDescription) resource.TestCheckFunc { +func testAccCheckPolicyExists(ctx context.Context, n string, v *elb.PolicyDescription) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceName] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", resourceName) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No Load Balancer Policy ID is set for %s", resourceName) + return fmt.Errorf("No ELB Classic Load Balancer Policy is set") } - loadBalancerName, policyName := tfelb.PolicyParseID(rs.Primary.ID) + lbName, policyName, err := tfelb.PolicyParseResourceID(rs.Primary.ID) - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - - input := &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(loadBalancerName), - PolicyNames: []*string{aws.String(policyName)}, + if err != nil { + return err } - output, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, input) + conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() + + output, err := tfelb.FindLoadBalancerPolicyByTwoPartKey(ctx, conn, lbName, policyName) if err != nil { return err } - if output == nil || len(output.PolicyDescriptions) == 0 { - return fmt.Errorf("Load Balancer Policy (%s) not found", rs.Primary.ID) - } - - *policyDescription = *output.PolicyDescriptions[0] + *output = *v return nil } @@ -296,93 +271,23 @@ func testAccCheckPolicyDestroy(ctx context.Context) resource.TestCheckFunc { continue } - loadBalancerName, policyName := tfelb.PolicyParseID(rs.Primary.ID) - out, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(loadBalancerName), - PolicyNames: []*string{aws.String(policyName)}, - }) + lbName, policyName, err := tfelb.PolicyParseResourceID(rs.Primary.ID) + if err != nil { - if ec2err, ok := err.(awserr.Error); ok && (ec2err.Code() == "PolicyNotFound" || ec2err.Code() == "LoadBalancerNotFound") { - continue - } return err } - if len(out.PolicyDescriptions) > 0 { - return fmt.Errorf("Policy still exists") - } - } - return nil - } -} - -func testAccCheckPolicyDisappears(ctx context.Context, loadBalancer *elb.LoadBalancerDescription, policy *elb.PolicyDescription) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() + _, err = tfelb.FindLoadBalancerPolicyByTwoPartKey(ctx, conn, lbName, policyName) - input := elb.DeleteLoadBalancerPolicyInput{ - LoadBalancerName: loadBalancer.LoadBalancerName, - PolicyName: policy.PolicyName, - } - _, err := conn.DeleteLoadBalancerPolicyWithContext(ctx, &input) - - return err - } -} - -func testAccCheckPolicyState(ctx context.Context, elbResource string, policyResource string) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[elbResource] - if !ok { - return fmt.Errorf("Not found: %s", elbResource) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - policy, ok := s.RootModule().Resources[policyResource] - if !ok { - return fmt.Errorf("Not found: %s", policyResource) - } - - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - loadBalancerName, policyName := tfelb.PolicyParseID(policy.Primary.ID) - loadBalancerPolicies, err := conn.DescribeLoadBalancerPoliciesWithContext(ctx, &elb.DescribeLoadBalancerPoliciesInput{ - LoadBalancerName: aws.String(loadBalancerName), - PolicyNames: []*string{aws.String(policyName)}, - }) - - if err != nil { - return err - } + if tfresource.NotFound(err) { + continue + } - for _, loadBalancerPolicy := range loadBalancerPolicies.PolicyDescriptions { - if *loadBalancerPolicy.PolicyName == policyName { - if *loadBalancerPolicy.PolicyTypeName != policy.Primary.Attributes["policy_type_name"] { - return fmt.Errorf("PolicyTypeName does not match") - } - policyAttributeCount, err := strconv.Atoi(policy.Primary.Attributes["policy_attribute.#"]) - if err != nil { - return err - } - if len(loadBalancerPolicy.PolicyAttributeDescriptions) != policyAttributeCount { - return fmt.Errorf("PolicyAttributeDescriptions length mismatch") - } - policyAttributes := make(map[string]string) - for k, v := range policy.Primary.Attributes { - if strings.HasPrefix(k, "policy_attribute.") && strings.HasSuffix(k, ".name") { - key := v - value_key := fmt.Sprintf("%s.value", strings.TrimSuffix(k, ".name")) - policyAttributes[key] = policy.Primary.Attributes[value_key] - } - } - for _, policyAttribute := range loadBalancerPolicy.PolicyAttributeDescriptions { - if *policyAttribute.AttributeValue != policyAttributes[*policyAttribute.AttributeName] { - return fmt.Errorf("PollicyAttribute Value mismatch %s != %s: %s", *policyAttribute.AttributeValue, policyAttributes[*policyAttribute.AttributeName], policyAttributes) - } - } + if err != nil { + return err } + + return fmt.Errorf("ELB Classic Load Balancer Policy %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/elb/sweep.go b/internal/service/elb/sweep.go index 1997c0a98f9..137829cc761 100644 --- a/internal/service/elb/sweep.go +++ b/internal/service/elb/sweep.go @@ -7,6 +7,7 @@ import ( "fmt" "log" + "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/elb" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-provider-aws/internal/conns" @@ -24,39 +25,42 @@ func sweepLoadBalancers(region string) error { ctx := sweep.Context(region) client, err := sweep.SharedRegionalSweepClient(region) if err != nil { - return fmt.Errorf("getting client: %s", err) + return fmt.Errorf("error getting client: %s", err) } conn := client.(*conns.AWSClient).ELBConn() + input := &elb.DescribeLoadBalancersInput{} + sweepResources := make([]sweep.Sweepable, 0) - err = conn.DescribeLoadBalancersPagesWithContext(ctx, &elb.DescribeLoadBalancersInput{}, func(out *elb.DescribeLoadBalancersOutput, lastPage bool) bool { - if len(out.LoadBalancerDescriptions) == 0 { - log.Println("[INFO] No ELBs found for sweeping") - return false + err = conn.DescribeLoadBalancersPagesWithContext(ctx, input, func(page *elb.DescribeLoadBalancersOutput, lastPage bool) bool { + if page == nil { + return !lastPage } - for _, lb := range out.LoadBalancerDescriptions { - log.Printf("[INFO] Deleting ELB: %s", *lb.LoadBalancerName) - - _, err := conn.DeleteLoadBalancerWithContext(ctx, &elb.DeleteLoadBalancerInput{ - LoadBalancerName: lb.LoadBalancerName, - }) - if err != nil { - log.Printf("[ERROR] Failed to delete ELB %s: %s", *lb.LoadBalancerName, err) - continue - } - err = CleanupNetworkInterfaces(ctx, client.(*conns.AWSClient).EC2Conn(), *lb.LoadBalancerName) - if err != nil { - log.Printf("[WARN] Failed to cleanup ENIs for ELB %q: %s", *lb.LoadBalancerName, err) - } + for _, v := range page.LoadBalancerDescriptions { + r := ResourceLoadBalancer() + d := r.Data(nil) + d.SetId(aws.StringValue(v.LoadBalancerName)) + + sweepResources = append(sweepResources, sweep.NewSweepResource(r, d, client)) } + return !lastPage }) + + if sweep.SkipSweepError(err) { + log.Printf("[WARN] Skipping ELB Classic Load Balancer sweep for %s: %s", region, err) + return nil + } + if err != nil { - if sweep.SkipSweepError(err) { - log.Printf("[WARN] Skipping ELB sweep for %s: %s", region, err) - return nil - } - return fmt.Errorf("Error retrieving ELBs: %s", err) + return fmt.Errorf("error listing ELB Classic Load Balancers (%s): %w", region, err) } + + err = sweep.SweepOrchestratorWithContext(ctx, sweepResources) + + if err != nil { + return fmt.Errorf("error sweeping ELB Classic Load Balancers (%s): %w", region, err) + } + return nil } diff --git a/internal/service/emr/instance_fleet.go b/internal/service/emr/instance_fleet.go index c4c929eff75..e24cd73a0ca 100644 --- a/internal/service/emr/instance_fleet.go +++ b/internal/service/emr/instance_fleet.go @@ -16,6 +16,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceInstanceFleet() *schema.Resource { @@ -24,8 +25,9 @@ func ResourceInstanceFleet() *schema.Resource { ReadWithoutTimeout: resourceInstanceFleetRead, UpdateWithoutTimeout: resourceInstanceFleetUpdate, DeleteWithoutTimeout: resourceInstanceFleetDelete, + Importer: &schema.ResourceImporter{ - StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + StateContext: func(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { idParts := strings.Split(d.Id(), "/") if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { return nil, fmt.Errorf("Unexpected format of ID (%q), expected cluster-id/fleet-id", d.Id()) @@ -37,6 +39,7 @@ func ResourceInstanceFleet() *schema.Resource { return []*schema.ResourceData{d}, nil }, }, + Schema: map[string]*schema.Schema{ "cluster_id": { Type: schema.TypeString, @@ -192,6 +195,14 @@ func ResourceInstanceFleet() *schema.Resource { Optional: true, ForceNew: true, }, + "provisioned_on_demand_capacity": { + Type: schema.TypeInt, + Computed: true, + }, + "provisioned_spot_capacity": { + Type: schema.TypeInt, + Computed: true, + }, "target_on_demand_capacity": { Type: schema.TypeInt, Optional: true, @@ -202,14 +213,6 @@ func ResourceInstanceFleet() *schema.Resource { Optional: true, Default: 0, }, - "provisioned_on_demand_capacity": { - Type: schema.TypeInt, - Computed: true, - }, - "provisioned_spot_capacity": { - Type: schema.TypeInt, - Computed: true, - }, }, } } @@ -218,10 +221,6 @@ func resourceInstanceFleetCreate(ctx context.Context, d *schema.ResourceData, me var diags diag.Diagnostics conn := meta.(*conns.AWSClient).EMRConn() - addInstanceFleetInput := &emr.AddInstanceFleetInput{ - ClusterId: aws.String(d.Get("cluster_id").(string)), - } - taskFleet := map[string]interface{}{ "name": d.Get("name"), "target_on_demand_capacity": d.Get("target_on_demand_capacity"), @@ -229,47 +228,41 @@ func resourceInstanceFleetCreate(ctx context.Context, d *schema.ResourceData, me "instance_type_configs": d.Get("instance_type_configs"), "launch_specifications": d.Get("launch_specifications"), } - addInstanceFleetInput.InstanceFleet = readInstanceFleetConfig(taskFleet, emr.InstanceFleetTypeTask) + input := &emr.AddInstanceFleetInput{ + ClusterId: aws.String(d.Get("cluster_id").(string)), + InstanceFleet: readInstanceFleetConfig(taskFleet, emr.InstanceFleetTypeTask), + } + + output, err := conn.AddInstanceFleetWithContext(ctx, input) - log.Printf("[DEBUG] Creating EMR instance fleet params: %s", addInstanceFleetInput) - resp, err := conn.AddInstanceFleetWithContext(ctx, addInstanceFleetInput) if err != nil { - return sdkdiag.AppendErrorf(diags, "adding EMR Instance Fleet: %s", err) + return sdkdiag.AppendErrorf(diags, "creating EMR Instance Fleet: %s", err) } - log.Printf("[DEBUG] Created EMR instance fleet finished: %#v", resp) - if resp == nil { - return sdkdiag.AppendErrorf(diags, "creating instance fleet: no instance fleet returned") - } - d.SetId(aws.StringValue(resp.InstanceFleetId)) + d.SetId(aws.StringValue(output.InstanceFleetId)) - return diags + return append(diags, resourceInstanceFleetRead(ctx, d, meta)...) } func resourceInstanceFleetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).EMRConn() - instanceFleets, err := FetchAllInstanceFleets(ctx, conn, d.Get("cluster_id").(string)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "listing EMR Instance Fleets for Cluster (%s): %s", d.Get("cluster_id").(string), err) - } + fleet, err := FindInstanceFleetByTwoPartKey(ctx, conn, d.Get("cluster_id").(string), d.Id()) - fleet := FindInstanceFleetByID(instanceFleets, d.Id()) - if fleet == nil { - if d.IsNewResource() { - return sdkdiag.AppendErrorf(diags, "finding EMR Instance Fleet (%s): not found after creation", d.Id()) - } - - log.Printf("[DEBUG] EMR Instance Fleet (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] EMR Instance Fleet (%s) not found, removing from state", d.Id()) d.SetId("") return diags } + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading EMR Instance Fleet (%s): %s", d.Id(), err) + } + if err := d.Set("instance_type_configs", flatteninstanceTypeConfigs(fleet.InstanceTypeSpecifications)); err != nil { return sdkdiag.AppendErrorf(diags, "setting instance_type_configs: %s", err) } - if err := d.Set("launch_specifications", flattenLaunchSpecifications(fleet.LaunchSpecifications)); err != nil { return sdkdiag.AppendErrorf(diags, "setting launch_specifications: %s", err) } @@ -278,103 +271,119 @@ func resourceInstanceFleetRead(ctx context.Context, d *schema.ResourceData, meta d.Set("provisioned_spot_capacity", fleet.ProvisionedSpotCapacity) d.Set("target_on_demand_capacity", fleet.TargetOnDemandCapacity) d.Set("target_spot_capacity", fleet.TargetSpotCapacity) - return diags -} -func FindInstanceFleetByID(instanceFleets []*emr.InstanceFleet, fleetId string) *emr.InstanceFleet { - for _, fleet := range instanceFleets { - if fleet != nil && aws.StringValue(fleet.Id) == fleetId { - return fleet - } - } - return nil + return diags } func resourceInstanceFleetUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).EMRConn() - log.Printf("[DEBUG] Modify EMR task fleet") - modifyConfig := &emr.InstanceFleetModifyConfig{ InstanceFleetId: aws.String(d.Id()), TargetOnDemandCapacity: aws.Int64(int64(d.Get("target_on_demand_capacity").(int))), TargetSpotCapacity: aws.Int64(int64(d.Get("target_spot_capacity").(int))), } - - modifyInstanceFleetInput := &emr.ModifyInstanceFleetInput{ + input := &emr.ModifyInstanceFleetInput{ ClusterId: aws.String(d.Get("cluster_id").(string)), InstanceFleet: modifyConfig, } - _, err := conn.ModifyInstanceFleetWithContext(ctx, modifyInstanceFleetInput) + _, err := conn.ModifyInstanceFleetWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying EMR Instance Fleet (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating EMR Instance Fleet (%s): %s", d.Id(), err) } stateConf := &resource.StateChangeConf{ Pending: []string{emr.InstanceFleetStateProvisioning, emr.InstanceFleetStateBootstrapping, emr.InstanceFleetStateResizing}, Target: []string{emr.InstanceFleetStateRunning}, - Refresh: instanceFleetStateRefresh(ctx, conn, d.Get("cluster_id").(string), d.Id()), + Refresh: statusInstanceFleet(ctx, conn, d.Get("cluster_id").(string), d.Id()), Timeout: 75 * time.Minute, Delay: 10 * time.Second, MinTimeout: 30 * time.Second, } _, err = stateConf.WaitForStateContext(ctx) + if err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for instance (%s) to terminate: %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "waiting for EMR Instance Fleet (%s) update: %s", d.Id(), err) } return append(diags, resourceInstanceFleetRead(ctx, d, meta)...) } -func instanceFleetStateRefresh(ctx context.Context, conn *emr.EMR, clusterID, ifID string) resource.StateRefreshFunc { - return func() (interface{}, string, error) { - instanceFleets, err := FetchAllInstanceFleets(ctx, conn, clusterID) - if err != nil { - return nil, "Not Found", err - } - - fleet := FindInstanceFleetByID(instanceFleets, ifID) - if fleet == nil { - return nil, "Not Found", err - } - - if fleet.Status == nil || fleet.Status.State == nil { - log.Printf("[WARN] ERM Instance Fleet found, but without state") - return nil, "Undefined", fmt.Errorf("undefined EMR Cluster Instance Fleet state") - } - - return fleet, *fleet.Status.State, nil - } -} - func resourceInstanceFleetDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - log.Printf("[WARN] AWS EMR Instance Fleet does not support DELETE; resizing cluster to zero before removing from state") conn := meta.(*conns.AWSClient).EMRConn() - clusterId := d.Get("cluster_id").(string) - - modifyInstanceFleetInput := &emr.ModifyInstanceFleetInput{ - ClusterId: aws.String(clusterId), + // AWS EMR Instance Fleet does not support DELETE; resizing cluster to zero before removing from state. + log.Printf("[DEBUG] Deleting EMR Instance Fleet: %s", d.Id()) + _, err := conn.ModifyInstanceFleetWithContext(ctx, &emr.ModifyInstanceFleetInput{ + ClusterId: aws.String(d.Get("cluster_id").(string)), InstanceFleet: &emr.InstanceFleetModifyConfig{ InstanceFleetId: aws.String(d.Id()), TargetOnDemandCapacity: aws.Int64(0), TargetSpotCapacity: aws.Int64(0), }, - } - - _, err := conn.ModifyInstanceFleetWithContext(ctx, modifyInstanceFleetInput) + }) if tfawserr.ErrMessageContains(err, emr.ErrCodeInvalidRequestException, "instance fleet may only be modified when the cluster is running or waiting") { return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting/modifying EMR Instance Fleet (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting EMR Instance Fleet (%s): %s", d.Id(), err) } return diags } + +func FindInstanceFleetByTwoPartKey(ctx context.Context, conn *emr.EMR, clusterID, fleetID string) (*emr.InstanceFleet, error) { + input := &emr.ListInstanceFleetsInput{ + ClusterId: aws.String(clusterID), + } + var fleets []*emr.InstanceFleet + + err := conn.ListInstanceFleetsPagesWithContext(ctx, input, func(page *emr.ListInstanceFleetsOutput, lastPage bool) bool { + if page == nil { + return !lastPage + } + + for _, v := range page.InstanceFleets { + if v != nil && v.Status != nil { + fleets = append(fleets, v) + } + } + + return !lastPage + }) + + if err != nil { + return nil, err + } + + for _, fleet := range fleets { + if aws.StringValue(fleet.Id) == fleetID { + return fleet, nil + } + } + + return nil, &resource.NotFoundError{} +} + +func statusInstanceFleet(ctx context.Context, conn *emr.EMR, clusterID, fleetID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := FindInstanceFleetByTwoPartKey(ctx, conn, clusterID, fleetID) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, aws.StringValue(output.Status.State), nil + } +} diff --git a/internal/service/emr/instance_fleet_test.go b/internal/service/emr/instance_fleet_test.go index 1a77c56b72e..5b67de2b0b8 100644 --- a/internal/service/emr/instance_fleet_test.go +++ b/internal/service/emr/instance_fleet_test.go @@ -3,11 +3,8 @@ package emr_test import ( "context" "fmt" - "log" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/emr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -22,11 +19,12 @@ func TestAccEMRInstanceFleet_basic(t *testing.T) { var fleet emr.InstanceFleet rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_emr_instance_fleet.task" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceFleetDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceFleetConfig_basic(rName), @@ -51,11 +49,12 @@ func TestAccEMRInstanceFleet_Zero_count(t *testing.T) { var fleet emr.InstanceFleet rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_emr_instance_fleet.task" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceFleetDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceFleetConfig_basic(rName), @@ -88,11 +87,12 @@ func TestAccEMRInstanceFleet_ebsBasic(t *testing.T) { var fleet emr.InstanceFleet rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_emr_instance_fleet.task" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceFleetDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceFleetConfig_ebsBasic(rName), @@ -117,11 +117,12 @@ func TestAccEMRInstanceFleet_full(t *testing.T) { var fleet emr.InstanceFleet rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_emr_instance_fleet.task" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceFleetDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceFleetConfig_full(rName), @@ -141,88 +142,27 @@ func TestAccEMRInstanceFleet_full(t *testing.T) { }) } -func TestAccEMRInstanceFleet_disappears(t *testing.T) { - ctx := acctest.Context(t) - var fleet emr.InstanceFleet - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - resourceName := "aws_emr_instance_fleet.task" - emrClusterResourceName := "aws_emr_cluster.test" - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceFleetDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccInstanceFleetConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( - testAccCheckInstanceFleetExists(ctx, resourceName, &fleet), - // EMR Instance Fleet can only be scaled down and are not removed until the - // Cluster is removed. Verify EMR Cluster disappearance handling. - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfemr.ResourceCluster(), emrClusterResourceName), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - -func testAccCheckInstanceFleetDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EMRConn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_emr_cluster" { - continue - } - - params := &emr.DescribeClusterInput{ - ClusterId: aws.String(rs.Primary.ID), - } - - describe, err := conn.DescribeClusterWithContext(ctx, params) - - if err == nil { - if describe.Cluster != nil && - *describe.Cluster.Status.State == "WAITING" { - return fmt.Errorf("EMR Cluster still exists") - } - } - - providerErr, ok := err.(awserr.Error) - if !ok { - return err - } - - log.Printf("[ERROR] %v", providerErr) - } - - return nil - } -} - func testAccCheckInstanceFleetExists(ctx context.Context, n string, v *emr.InstanceFleet) 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 task fleet id set") + return fmt.Errorf("No EMR Instance Fleet ID is set") } - meta := acctest.Provider.Meta() - conn := meta.(*conns.AWSClient).EMRConn() - instanceFleets, err := tfemr.FetchAllInstanceFleets(ctx, conn, rs.Primary.Attributes["cluster_id"]) + + conn := acctest.Provider.Meta().(*conns.AWSClient).EMRConn() + + output, err := tfemr.FindInstanceFleetByTwoPartKey(ctx, conn, rs.Primary.Attributes["cluster_id"], rs.Primary.ID) + if err != nil { - return fmt.Errorf("EMR error: %v", err) + return err } - fleet := tfemr.FindInstanceFleetByID(instanceFleets, rs.Primary.ID) - if fleet == nil { - return fmt.Errorf("No match found for (%s)", n) - } - v = fleet + *v = *output + return nil } } @@ -238,24 +178,14 @@ func testAccInstanceFleetResourceImportStateIdFunc(resourceName string) resource } } -const testAccInstanceFleetBase = ` -data "aws_availability_zones" "available" { - # Many instance types are not available in this availability zone - exclude_zone_ids = ["usw2-az4"] - state = "available" - - filter { - name = "opt-in-status" - values = ["opt-in-not-required"] - } -} - +func testAccInstanceFleetConfig_base(rName string) string { + return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptInDefaultExclude(), fmt.Sprintf(` resource "aws_vpc" "test" { cidr_block = "10.0.0.0/16" enable_dns_hostnames = true tags = { - Name = "tf-acc-test-emr-cluster" + Name = %[1]q } } @@ -263,12 +193,13 @@ resource "aws_internet_gateway" "test" { vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test-emr-cluster" + Name = %[1]q } } resource "aws_security_group" "test" { vpc_id = aws_vpc.test.id + name = %[1]q ingress { from_port = 0 @@ -285,7 +216,7 @@ resource "aws_security_group" "test" { } tags = { - Name = "tf-acc-test-emr-cluster" + Name = %[1]q } # EMR will modify ingress rules @@ -301,7 +232,7 @@ resource "aws_subnet" "test" { vpc_id = aws_vpc.test.id tags = { - Name = "tf-acc-test-emr-cluster" + Name = %[1]q } } @@ -312,6 +243,10 @@ resource "aws_route_table" "test" { cidr_block = "0.0.0.0/0" gateway_id = aws_internet_gateway.test.id } + + tags = { + Name = %[1]q + } } resource "aws_route_table_association" "test" { @@ -457,10 +392,11 @@ resource "aws_emr_cluster" "test" { instance_profile = aws_iam_instance_profile.emr_instance_profile.arn } } -` +`, rName)) +} -func testAccInstanceFleetConfig_basic(r string) string { - return fmt.Sprintf(testAccInstanceFleetBase+` +func testAccInstanceFleetConfig_basic(rName string) string { + return acctest.ConfigCompose(testAccInstanceFleetConfig_base(rName), fmt.Sprintf(` resource "aws_emr_instance_fleet" "task" { cluster_id = aws_emr_cluster.test.id @@ -479,11 +415,11 @@ resource "aws_emr_instance_fleet" "task" { target_on_demand_capacity = 1 target_spot_capacity = 0 } -`, r) +`, rName)) } -func testAccInstanceFleetConfig_zeroCount(r string) string { - return fmt.Sprintf(testAccInstanceFleetBase+` +func testAccInstanceFleetConfig_zeroCount(rName string) string { + return acctest.ConfigCompose(testAccInstanceFleetConfig_base(rName), fmt.Sprintf(` resource "aws_emr_instance_fleet" "task" { cluster_id = aws_emr_cluster.test.id @@ -502,11 +438,11 @@ resource "aws_emr_instance_fleet" "task" { target_on_demand_capacity = 0 target_spot_capacity = 0 } -`, r) +`, rName)) } -func testAccInstanceFleetConfig_ebsBasic(r string) string { - return fmt.Sprintf(testAccInstanceFleetBase+` +func testAccInstanceFleetConfig_ebsBasic(rName string) string { + return acctest.ConfigCompose(testAccInstanceFleetConfig_base(rName), fmt.Sprintf(` resource "aws_emr_instance_fleet" "task" { cluster_id = aws_emr_cluster.test.id @@ -534,11 +470,11 @@ resource "aws_emr_instance_fleet" "task" { target_on_demand_capacity = 0 target_spot_capacity = 1 } -`, r) +`, rName)) } -func testAccInstanceFleetConfig_full(r string) string { - return fmt.Sprintf(testAccInstanceFleetBase+` +func testAccInstanceFleetConfig_full(rName string) string { + return acctest.ConfigCompose(testAccInstanceFleetConfig_base(rName), fmt.Sprintf(` resource "aws_emr_instance_fleet" "task" { cluster_id = aws_emr_cluster.test.id @@ -586,5 +522,5 @@ resource "aws_emr_instance_fleet" "task" { target_on_demand_capacity = 2 target_spot_capacity = 2 } -`, r) +`, rName)) } diff --git a/internal/service/emr/instance_group_test.go b/internal/service/emr/instance_group_test.go index 68429ad2d2a..2442d48343c 100644 --- a/internal/service/emr/instance_group_test.go +++ b/internal/service/emr/instance_group_test.go @@ -3,11 +3,9 @@ package emr_test import ( "context" "fmt" - "log" "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/emr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -27,7 +25,7 @@ func TestAccEMRInstanceGroup_basic(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_basic(rName), @@ -59,7 +57,7 @@ func TestAccEMRInstanceGroup_disappears(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_basic(rName), @@ -87,7 +85,7 @@ func TestAccEMRInstanceGroup_Disappears_emrCluster(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_basic(rName), @@ -112,7 +110,7 @@ func TestAccEMRInstanceGroup_bidPrice(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_basic(rName), @@ -165,7 +163,7 @@ func TestAccEMRInstanceGroup_sJSON(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_configurationsJSON(rName, "partitionName1"), @@ -209,7 +207,7 @@ func TestAccEMRInstanceGroup_autoScalingPolicy(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_autoScalingPolicy(rName, 1, 3), @@ -255,7 +253,7 @@ func TestAccEMRInstanceGroup_instanceCount(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_basic(rName), @@ -286,7 +284,7 @@ func TestAccEMRInstanceGroup_EBS_ebsOptimized(t *testing.T) { PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, emr.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckInstanceGroupDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccInstanceGroupConfig_ebs(rName, true), @@ -315,38 +313,6 @@ func TestAccEMRInstanceGroup_EBS_ebsOptimized(t *testing.T) { }) } -func testAccCheckInstanceGroupDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).EMRConn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_emr_cluster" { - continue - } - - params := &emr.DescribeClusterInput{ - ClusterId: aws.String(rs.Primary.ID), - } - - describe, err := conn.DescribeClusterWithContext(ctx, params) - - if err == nil { - if describe.Cluster != nil && - *describe.Cluster.Status.State == "WAITING" { - return fmt.Errorf("EMR Cluster still exists") - } - } - - if providerErr, ok := err.(awserr.Error); !ok { - log.Printf("[ERROR] %v", providerErr) - return err - } - } - - return nil - } -} - func testAccCheckInstanceGroupExists(ctx context.Context, name string, ig *emr.InstanceGroup) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[name] diff --git a/internal/service/iam/account_password_policy.go b/internal/service/iam/account_password_policy.go index e43bf34f3bf..3ea5a460e2a 100644 --- a/internal/service/iam/account_password_policy.go +++ b/internal/service/iam/account_password_policy.go @@ -8,9 +8,11 @@ import ( "github.com/aws/aws-sdk-go/service/iam" "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceAccountPasswordPolicy() *schema.Resource { @@ -19,6 +21,7 @@ func ResourceAccountPasswordPolicy() *schema.Resource { ReadWithoutTimeout: resourceAccountPasswordPolicyRead, UpdateWithoutTimeout: resourceAccountPasswordPolicyUpdate, DeleteWithoutTimeout: resourceAccountPasswordPolicyDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -112,12 +115,14 @@ func resourceAccountPasswordPolicyUpdate(ctx context.Context, d *schema.Resource } _, err := conn.UpdateAccountPasswordPolicyWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "updating IAM Password Policy: %s", err) + return sdkdiag.AppendErrorf(diags, "updating IAM Account Password Policy: %s", err) } - log.Println("[DEBUG] IAM account password policy updated") - d.SetId("iam-account-password-policy") + if d.IsNewResource() { + d.SetId("iam-account-password-policy") + } return append(diags, resourceAccountPasswordPolicyRead(ctx, d, meta)...) } @@ -126,18 +131,17 @@ func resourceAccountPasswordPolicyRead(ctx context.Context, d *schema.ResourceDa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).IAMConn() - input := &iam.GetAccountPasswordPolicyInput{} - resp, err := conn.GetAccountPasswordPolicyWithContext(ctx, input) - if err != nil { - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - log.Printf("[WARN] IAM Account Password Policy not found, removing from state") - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "reading IAM account password policy: %s", err) + policy, err := FindAccountPasswordPolicy(ctx, conn) + + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + log.Printf("[WARN] IAM Account Password Policy (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - policy := resp.PasswordPolicy + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading IAM Account Password Policy (%s): %s", d.Id(), err) + } d.Set("allow_users_to_change_password", policy.AllowUsersToChangePassword) d.Set("expire_passwords", policy.ExpirePasswords) @@ -157,12 +161,35 @@ func resourceAccountPasswordPolicyDelete(ctx context.Context, d *schema.Resource var diags diag.Diagnostics conn := meta.(*conns.AWSClient).IAMConn() - log.Println("[DEBUG] Deleting IAM account password policy") - input := &iam.DeleteAccountPasswordPolicyInput{} - if _, err := conn.DeleteAccountPasswordPolicyWithContext(ctx, input); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting IAM Password Policy: %s", err) + log.Printf("[DEBUG] Deleting IAM Account Password Policy: %s", d.Id()) + _, err := conn.DeleteAccountPasswordPolicyWithContext(ctx, &iam.DeleteAccountPasswordPolicyInput{}) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting IAM Account Password Policy (%s): %s", d.Id(), err) } - log.Println("[DEBUG] Deleted IAM account password policy") return diags } + +func FindAccountPasswordPolicy(ctx context.Context, conn *iam.IAM) (*iam.PasswordPolicy, error) { + input := &iam.GetAccountPasswordPolicyInput{} + + output, err := conn.GetAccountPasswordPolicyWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.PasswordPolicy == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.PasswordPolicy, nil +} diff --git a/internal/service/iam/account_password_policy_test.go b/internal/service/iam/account_password_policy_test.go index b3d994f49b3..9647095220a 100644 --- a/internal/service/iam/account_password_policy_test.go +++ b/internal/service/iam/account_password_policy_test.go @@ -5,20 +5,32 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" "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" + tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -func TestAccIAMAccountPasswordPolicy_basic(t *testing.T) { +func TestAccIAMAccountPasswordPolicy_serial(t *testing.T) { + t.Parallel() + + testCases := map[string]func(t *testing.T){ + "basic": testAccAccountPasswordPolicy_basic, + "disappears": testAccAccountPasswordPolicy_disappears, + } + + acctest.RunSerialTests1Level(t, testCases, 0) +} + +func testAccAccountPasswordPolicy_basic(t *testing.T) { ctx := acctest.Context(t) - var policy iam.GetAccountPasswordPolicyOutput + var policy iam.PasswordPolicy resourceName := "aws_iam_account_password_policy.test" - resource.ParallelTest(t, resource.TestCase{ + resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, ErrorCheck: acctest.ErrorCheck(t, iam.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -47,6 +59,29 @@ func TestAccIAMAccountPasswordPolicy_basic(t *testing.T) { }) } +func testAccAccountPasswordPolicy_disappears(t *testing.T) { + ctx := acctest.Context(t) + var policy iam.PasswordPolicy + resourceName := "aws_iam_account_password_policy.test" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iam.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAccountPasswordPolicyDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAccountPasswordPolicyConfig_basic, + Check: resource.ComposeTestCheckFunc( + testAccCheckAccountPasswordPolicyExists(ctx, resourceName, &policy), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfiam.ResourceAccountPasswordPolicy(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + func testAccCheckAccountPasswordPolicyDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).IAMConn() @@ -56,27 +91,24 @@ func testAccCheckAccountPasswordPolicyDestroy(ctx context.Context) resource.Test continue } - // Try to get policy - _, err := conn.GetAccountPasswordPolicyWithContext(ctx, &iam.GetAccountPasswordPolicyInput{}) - if err == nil { - return fmt.Errorf("still exist.") - } + _, err := tfiam.FindAccountPasswordPolicy(ctx, conn) - // Verify the error is what we want - awsErr, ok := err.(awserr.Error) - if !ok { - return err + if tfresource.NotFound(err) { + continue } - if awsErr.Code() != "NoSuchEntity" { + + if err != nil { return err } + + return fmt.Errorf("IAM Account Password Policy %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckAccountPasswordPolicyExists(ctx context.Context, n string, res *iam.GetAccountPasswordPolicyOutput) resource.TestCheckFunc { +func testAccCheckAccountPasswordPolicyExists(ctx context.Context, n string, v *iam.PasswordPolicy) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -84,17 +116,18 @@ func testAccCheckAccountPasswordPolicyExists(ctx context.Context, n string, res } if rs.Primary.ID == "" { - return fmt.Errorf("No policy ID is set") + return fmt.Errorf("No IAM Account Password Policy ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).IAMConn() - resp, err := conn.GetAccountPasswordPolicyWithContext(ctx, &iam.GetAccountPasswordPolicyInput{}) + output, err := tfiam.FindAccountPasswordPolicy(ctx, conn) + if err != nil { return err } - *res = *resp + *v = *output return nil } diff --git a/internal/service/iam/group.go b/internal/service/iam/group.go index 04cfb4fb566..9286d68c8ff 100644 --- a/internal/service/iam/group.go +++ b/internal/service/iam/group.go @@ -24,6 +24,7 @@ func ResourceGroup() *schema.Resource { ReadWithoutTimeout: resourceGroupRead, UpdateWithoutTimeout: resourceGroupUpdate, DeleteWithoutTimeout: resourceGroupDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -33,10 +34,6 @@ func ResourceGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "unique_id": { - Type: schema.TypeString, - Computed: true, - }, "name": { Type: schema.TypeString, Required: true, @@ -50,6 +47,18 @@ func ResourceGroup() *schema.Resource { Optional: true, Default: "/", }, + "unique_id": { + Type: schema.TypeString, + Computed: true, + }, + }, + + CustomizeDiff: func(_ context.Context, d *schema.ResourceDiff, meta interface{}) error { + if d.HasChanges("name", "path") { + return d.SetNewComputed("arn") + } + + return nil }, } } @@ -57,19 +66,28 @@ func ResourceGroup() *schema.Resource { func resourceGroupCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).IAMConn() - name := d.Get("name").(string) - path := d.Get("path").(string) - request := &iam.CreateGroupInput{ - Path: aws.String(path), + name := d.Get("name").(string) + input := &iam.CreateGroupInput{ GroupName: aws.String(name), + Path: aws.String(d.Get("path").(string)), } - createResp, err := conn.CreateGroupWithContext(ctx, request) + output, err := conn.CreateGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating IAM Group %s: %s", name, err) + return sdkdiag.AppendErrorf(diags, "creating IAM Group (%s): %s", name, err) + } + + d.SetId(aws.StringValue(output.Group.GroupName)) + + _, err = tfresource.RetryWhenNotFound(ctx, propagationTimeout, func() (interface{}, error) { + return FindGroupByName(ctx, conn, d.Id()) + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for IAM Group (%s) create: %s", d.Id(), err) } - d.SetId(aws.StringValue(createResp.Group.GroupName)) return append(diags, resourceGroupRead(ctx, d, meta)...) } @@ -78,33 +96,9 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa var diags diag.Diagnostics conn := meta.(*conns.AWSClient).IAMConn() - request := &iam.GetGroupInput{ - GroupName: aws.String(d.Id()), - } - - var getResp *iam.GetGroupOutput - - err := resource.RetryContext(ctx, propagationTimeout, func() *resource.RetryError { - var err error - - getResp, err = conn.GetGroupWithContext(ctx, request) - - if d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { - return resource.RetryableError(err) - } - - if err != nil { - return resource.NonRetryableError(err) - } + group, err := FindGroupByName(ctx, conn, d.Id()) - return nil - }) - - if tfresource.TimedOut(err) { - getResp, err = conn.GetGroupWithContext(ctx, request) - } - - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] IAM Group (%s) not found, removing from state", d.Id()) d.SetId("") return diags @@ -114,55 +108,77 @@ func resourceGroupRead(ctx context.Context, d *schema.ResourceData, meta interfa return sdkdiag.AppendErrorf(diags, "reading IAM Group (%s): %s", d.Id(), err) } - if getResp == nil || getResp.Group == nil { - return sdkdiag.AppendErrorf(diags, "reading IAM Group (%s): empty response", d.Id()) - } - - group := getResp.Group - - d.Set("name", group.GroupName) d.Set("arn", group.Arn) + d.Set("name", group.GroupName) d.Set("path", group.Path) d.Set("unique_id", group.GroupId) + return diags } func resourceGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - if d.HasChanges("name", "path") { - conn := meta.(*conns.AWSClient).IAMConn() - on, nn := d.GetChange("name") - _, np := d.GetChange("path") - - request := &iam.UpdateGroupInput{ - GroupName: aws.String(on.(string)), - NewGroupName: aws.String(nn.(string)), - NewPath: aws.String(np.(string)), - } - _, err := conn.UpdateGroupWithContext(ctx, request) - if err != nil { - return sdkdiag.AppendErrorf(diags, "updating IAM Group %s: %s", d.Id(), err) - } - d.SetId(nn.(string)) - return append(diags, resourceGroupRead(ctx, d, meta)...) + conn := meta.(*conns.AWSClient).IAMConn() + + o, n := d.GetChange("name") + input := &iam.UpdateGroupInput{ + GroupName: aws.String(o.(string)), + NewGroupName: aws.String(n.(string)), + NewPath: aws.String(d.Get("path").(string)), } - return diags + + _, err := conn.UpdateGroupWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating IAM Group (%s): %s", d.Id(), err) + } + + d.SetId(n.(string)) + + return append(diags, resourceGroupRead(ctx, d, meta)...) } func resourceGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).IAMConn() - request := &iam.DeleteGroupInput{ + log.Printf("[DEBUG] Deleting IAM Group: %s", d.Id()) + _, err := conn.DeleteGroupWithContext(ctx, &iam.DeleteGroupInput{ GroupName: aws.String(d.Id()), - } + }) - if _, err := conn.DeleteGroupWithContext(ctx, request); err != nil { - return sdkdiag.AppendErrorf(diags, "deleting IAM Group %s: %s", d.Id(), err) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting IAM Group (%s): %s", d.Id(), err) } + return diags } +func FindGroupByName(ctx context.Context, conn *iam.IAM, name string) (*iam.Group, error) { + input := &iam.GetGroupInput{ + GroupName: aws.String(name), + } + + output, err := conn.GetGroupWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Group == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Group, nil +} + func DeleteGroupPolicyAttachments(ctx context.Context, conn *iam.IAM, groupName string) error { var attachedPolicies []*iam.AttachedPolicy input := &iam.ListAttachedGroupPoliciesInput{ diff --git a/internal/service/iam/group_test.go b/internal/service/iam/group_test.go index e8935bffa11..d764803b2cc 100644 --- a/internal/service/iam/group_test.go +++ b/internal/service/iam/group_test.go @@ -2,28 +2,24 @@ package iam_test import ( "context" - "errors" "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/iam" 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" + tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccIAMGroup_basic(t *testing.T) { ctx := acctest.Context(t) - var conf iam.GetGroupOutput + var conf iam.Group + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_iam_group.test" - resourceName2 := "aws_iam_group.test2" - rString := sdkacctest.RandString(8) - groupName := fmt.Sprintf("tf-acc-group-basic-%s", rString) - groupName2 := fmt.Sprintf("tf-acc-group-basic-2-%s", rString) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -32,10 +28,14 @@ func TestAccIAMGroup_basic(t *testing.T) { CheckDestroy: testAccCheckGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccGroupConfig_basic(groupName), - Check: resource.ComposeTestCheckFunc( + Config: testAccGroupConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckGroupExists(ctx, resourceName, &conf), - testAccCheckGroupAttributes(&conf, groupName, "/"), + // arn:${Partition}:iam::${Account}:group/${GroupNameWithPath} + acctest.CheckResourceAttrGlobalARN(resourceName, "arn", "iam", fmt.Sprintf("group/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "path", "/"), + resource.TestCheckResourceAttrSet(resourceName, "unique_id"), ), }, { @@ -43,12 +43,29 @@ func TestAccIAMGroup_basic(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + }, + }) +} + +func TestAccIAMGroup_disappears(t *testing.T) { + ctx := acctest.Context(t) + var conf iam.Group + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iam_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iam.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckGroupDestroy(ctx), + Steps: []resource.TestStep{ { - Config: testAccGroupConfig_2(groupName2), + Config: testAccGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckGroupExists(ctx, resourceName2, &conf), - testAccCheckGroupAttributes(&conf, groupName2, "/funnypath/"), + testAccCheckGroupExists(ctx, resourceName, &conf), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfiam.ResourceGroup(), resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) @@ -56,11 +73,10 @@ func TestAccIAMGroup_basic(t *testing.T) { func TestAccIAMGroup_nameChange(t *testing.T) { ctx := acctest.Context(t) - var conf iam.GetGroupOutput + var conf iam.Group + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_iam_group.test" - rString := sdkacctest.RandString(8) - groupName := fmt.Sprintf("tf-acc-group-basic-%s", rString) - groupName2 := fmt.Sprintf("tf-acc-group-basic-2-%s", rString) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -69,17 +85,58 @@ func TestAccIAMGroup_nameChange(t *testing.T) { CheckDestroy: testAccCheckGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccGroupConfig_basic(groupName), + Config: testAccGroupConfig_basic(rName1), Check: resource.ComposeTestCheckFunc( testAccCheckGroupExists(ctx, resourceName, &conf), - testAccCheckGroupAttributes(&conf, groupName, "/"), + acctest.CheckResourceAttrGlobalARN(resourceName, "arn", "iam", fmt.Sprintf("group/%s", rName1)), + resource.TestCheckResourceAttr(resourceName, "name", rName1), ), }, { - Config: testAccGroupConfig_basic(groupName2), + Config: testAccGroupConfig_basic(rName2), Check: resource.ComposeTestCheckFunc( testAccCheckGroupExists(ctx, resourceName, &conf), - testAccCheckGroupAttributes(&conf, groupName2, "/"), + acctest.CheckResourceAttrGlobalARN(resourceName, "arn", "iam", fmt.Sprintf("group/%s", rName2)), + resource.TestCheckResourceAttr(resourceName, "name", rName2), + ), + }, + }, + }) +} + +func TestAccIAMGroup_path(t *testing.T) { + ctx := acctest.Context(t) + var conf iam.Group + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_iam_group.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iam.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccGroupConfig_path(rName, "/path1/"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckGroupExists(ctx, resourceName, &conf), + acctest.CheckResourceAttrGlobalARN(resourceName, "arn", "iam", fmt.Sprintf("group/path1/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "path", "/path1/"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccGroupConfig_path(rName, "/path2/"), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckGroupExists(ctx, resourceName, &conf), + acctest.CheckResourceAttrGlobalARN(resourceName, "arn", "iam", fmt.Sprintf("group/path2/%s", rName)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "path", "/path2/"), ), }, }, @@ -95,29 +152,24 @@ func testAccCheckGroupDestroy(ctx context.Context) resource.TestCheckFunc { continue } - // Try to get group - _, err := conn.GetGroupWithContext(ctx, &iam.GetGroupInput{ - GroupName: aws.String(rs.Primary.ID), - }) - if err == nil { - return errors.New("still exist.") - } + _, err := tfiam.FindGroupByName(ctx, conn, rs.Primary.ID) - // Verify the error is what we want - ec2err, ok := err.(awserr.Error) - if !ok { - return err + if tfresource.NotFound(err) { + continue } - if ec2err.Code() != "NoSuchEntity" { + + if err != nil { return err } + + return fmt.Errorf("IAM Group %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckGroupExists(ctx context.Context, n string, res *iam.GetGroupOutput) resource.TestCheckFunc { +func testAccCheckGroupExists(ctx context.Context, n string, v *iam.Group) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -125,52 +177,36 @@ func testAccCheckGroupExists(ctx context.Context, n string, res *iam.GetGroupOut } if rs.Primary.ID == "" { - return errors.New("No Group name is set") + return fmt.Errorf("No IAM Group ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).IAMConn() - resp, err := conn.GetGroupWithContext(ctx, &iam.GetGroupInput{ - GroupName: aws.String(rs.Primary.ID), - }) + output, err := tfiam.FindGroupByName(ctx, conn, rs.Primary.ID) + if err != nil { return err } - *res = *resp - - return nil - } -} - -func testAccCheckGroupAttributes(group *iam.GetGroupOutput, name string, path string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *group.Group.GroupName != name { - return fmt.Errorf("Bad name: %s when %s was expected", *group.Group.GroupName, name) - } - - if *group.Group.Path != path { - return fmt.Errorf("Bad path: %s when %s was expected", *group.Group.Path, path) - } + *v = *output return nil } } -func testAccGroupConfig_basic(groupName string) string { +func testAccGroupConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_iam_group" "test" { - name = "%s" - path = "/" + name = %[1]q } -`, groupName) +`, rName) } -func testAccGroupConfig_2(groupName string) string { +func testAccGroupConfig_path(rName, path string) string { return fmt.Sprintf(` -resource "aws_iam_group" "test2" { - name = "%s" - path = "/funnypath/" +resource "aws_iam_group" "test" { + name = %[1]q + path = %[2]q } -`, groupName) +`, rName, path) } diff --git a/internal/service/iam/server_certificate.go b/internal/service/iam/server_certificate.go index 0f2fd56f85b..ddd70299bcb 100644 --- a/internal/service/iam/server_certificate.go +++ b/internal/service/iam/server_certificate.go @@ -1,17 +1,14 @@ package iam -import ( // nosemgrep:ci.aws-sdk-go-multiple-service-imports +import ( "context" "crypto/sha1" "encoding/hex" "log" - "regexp" "strings" "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" - "github.com/aws/aws-sdk-go/service/elb" "github.com/aws/aws-sdk-go/service/iam" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -19,6 +16,7 @@ import ( // nosemgrep:ci.aws-sdk-go-multiple-service-imports "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" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -31,11 +29,16 @@ func ResourceServerCertificate() *schema.Resource { ReadWithoutTimeout: resourceServerCertificateRead, UpdateWithoutTimeout: resourceServerCertificateUpdate, DeleteWithoutTimeout: resourceServerCertificateDelete, + Importer: &schema.ResourceImporter{ StateContext: resourceServerCertificateImport, }, Schema: map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "certificate_body": { Type: schema.TypeString, Required: true, @@ -43,7 +46,6 @@ func ResourceServerCertificate() *schema.Resource { DiffSuppressFunc: suppressNormalizeCertRemoval, StateFunc: StateTrimSpace, }, - "certificate_chain": { Type: schema.TypeString, Optional: true, @@ -51,23 +53,10 @@ func ResourceServerCertificate() *schema.Resource { DiffSuppressFunc: suppressNormalizeCertRemoval, StateFunc: StateTrimSpace, }, - - "path": { + "expiration": { Type: schema.TypeString, - Optional: true, - Default: "/", - ForceNew: true, - }, - - "private_key": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Sensitive: true, - DiffSuppressFunc: suppressNormalizeCertRemoval, - StateFunc: StateTrimSpace, + Computed: true, }, - "name": { Type: schema.TypeString, Optional: true, @@ -76,29 +65,34 @@ func ResourceServerCertificate() *schema.Resource { ConflictsWith: []string{"name_prefix"}, ValidateFunc: validation.StringLenBetween(0, 128), }, - "name_prefix": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, ConflictsWith: []string{"name"}, ValidateFunc: validation.StringLenBetween(0, 128-resource.UniqueIDSuffixLength), }, - - "arn": { + "path": { Type: schema.TypeString, - Computed: true, + Optional: true, + Default: "/", + ForceNew: true, }, - "expiration": { - Type: schema.TypeString, - Computed: true, + "private_key": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Sensitive: true, + DiffSuppressFunc: suppressNormalizeCertRemoval, + StateFunc: StateTrimSpace, }, + "tags": tftags.TagsSchema(), + "tags_all": tftags.TagsSchemaComputed(), "upload_date": { Type: schema.TypeString, Computed: true, }, - "tags": tftags.TagsSchema(), - "tags_all": tftags.TagsSchemaComputed(), }, CustomizeDiff: verify.SetTagsDiff, @@ -111,54 +105,45 @@ func resourceServerCertificateCreate(ctx context.Context, d *schema.ResourceData defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - var sslCertName string - if v, ok := d.GetOk("name"); ok { - sslCertName = v.(string) - } else if v, ok := d.GetOk("name_prefix"); ok { - sslCertName = resource.PrefixedUniqueId(v.(string)) - } else { - sslCertName = resource.UniqueId() - } - - createOpts := &iam.UploadServerCertificateInput{ + sslCertName := create.Name(d.Get("name").(string), d.Get("name_prefix").(string)) + input := &iam.UploadServerCertificateInput{ CertificateBody: aws.String(d.Get("certificate_body").(string)), PrivateKey: aws.String(d.Get("private_key").(string)), ServerCertificateName: aws.String(sslCertName), } - if len(tags) > 0 { - createOpts.Tags = Tags(tags.IgnoreAWS()) - } - if v, ok := d.GetOk("certificate_chain"); ok { - createOpts.CertificateChain = aws.String(v.(string)) + input.CertificateChain = aws.String(v.(string)) } if v, ok := d.GetOk("path"); ok { - createOpts.Path = aws.String(v.(string)) + input.Path = aws.String(v.(string)) + } + + if len(tags) > 0 { + input.Tags = Tags(tags.IgnoreAWS()) } - log.Printf("[DEBUG] Creating IAM Server Certificate with opts: %s", createOpts) - resp, err := conn.UploadServerCertificateWithContext(ctx, createOpts) + output, err := conn.UploadServerCertificateWithContext(ctx, input) // Some partitions (i.e., ISO) may not support tag-on-create - if createOpts.Tags != nil && verify.ErrorISOUnsupported(conn.PartitionID, err) { + if input.Tags != nil && verify.ErrorISOUnsupported(conn.PartitionID, err) { log.Printf("[WARN] failed creating IAM Server Certificate (%s) with tags: %s. Trying create without tags.", sslCertName, err) - createOpts.Tags = nil + input.Tags = nil - resp, err = conn.UploadServerCertificateWithContext(ctx, createOpts) + output, err = conn.UploadServerCertificateWithContext(ctx, input) } if err != nil { - return sdkdiag.AppendErrorf(diags, "uploading server certificate: %s", err) + return sdkdiag.AppendErrorf(diags, "creating IAM Server Certificate (%s): %s", sslCertName, err) } - d.SetId(aws.StringValue(resp.ServerCertificateMetadata.ServerCertificateId)) - d.Set("name", sslCertName) + d.SetId(aws.StringValue(output.ServerCertificateMetadata.ServerCertificateId)) + d.Set("name", sslCertName) // Required for resource Read. // Some partitions (i.e., ISO) may not support tag-on-create, attempt tag after create - if createOpts.Tags == nil && len(tags) > 0 { - err := serverCertificateUpdateTags(ctx, conn, d.Get("name").(string), nil, tags) + if input.Tags == nil && len(tags) > 0 { + err := serverCertificateUpdateTags(ctx, conn, sslCertName, nil, tags) // If default tags only, log and continue. Otherwise, error. if v, ok := d.GetOk("tags"); (!ok || len(v.(map[string]interface{})) == 0) && verify.ErrorISOUnsupported(conn.PartitionID, err) { @@ -180,11 +165,9 @@ func resourceServerCertificateRead(ctx context.Context, d *schema.ResourceData, defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - resp, err := conn.GetServerCertificateWithContext(ctx, &iam.GetServerCertificateInput{ - ServerCertificateName: aws.String(d.Get("name").(string)), - }) + cert, err := FindServerCertificateByName(ctx, conn, d.Get("name").(string)) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] IAM Server Certificate (%s) not found, removing from state", d.Id()) d.SetId("") return diags @@ -194,20 +177,19 @@ func resourceServerCertificateRead(ctx context.Context, d *schema.ResourceData, return sdkdiag.AppendErrorf(diags, "reading IAM Server Certificate (%s): %s", d.Id(), err) } - cert := resp.ServerCertificate metadata := cert.ServerCertificateMetadata d.SetId(aws.StringValue(metadata.ServerCertificateId)) - + d.Set("arn", metadata.Arn) d.Set("certificate_body", cert.CertificateBody) d.Set("certificate_chain", cert.CertificateChain) - d.Set("path", metadata.Path) - d.Set("arn", metadata.Arn) if metadata.Expiration != nil { d.Set("expiration", aws.TimeValue(metadata.Expiration).Format(time.RFC3339)) } else { d.Set("expiration", nil) } - + d.Set("name", metadata.ServerCertificateName) + d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(metadata.ServerCertificateName))) + d.Set("path", metadata.Path) if metadata.UploadDate != nil { d.Set("upload_date", aws.TimeValue(metadata.UploadDate).Format(time.RFC3339)) } else { @@ -254,32 +236,16 @@ func resourceServerCertificateUpdate(ctx context.Context, d *schema.ResourceData func resourceServerCertificateDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).IAMConn() - log.Printf("[INFO] Deleting IAM Server Certificate: %s", d.Id()) - err := resource.RetryContext(ctx, 15*time.Minute, func() *resource.RetryError { - _, err := conn.DeleteServerCertificateWithContext(ctx, &iam.DeleteServerCertificateInput{ - ServerCertificateName: aws.String(d.Get("name").(string)), - }) - - if err != nil { - if awsErr, ok := err.(awserr.Error); ok { - if awsErr.Code() == iam.ErrCodeDeleteConflictException && strings.Contains(awsErr.Message(), "currently in use by arn") { - currentlyInUseBy(ctx, awsErr.Message(), meta.(*conns.AWSClient).ELBConn()) - log.Printf("[WARN] Conflict deleting server certificate: %s, retrying", awsErr.Message()) - return resource.RetryableError(err) - } - if awsErr.Code() == iam.ErrCodeNoSuchEntityException { - return nil - } - } - return resource.NonRetryableError(err) - } - return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.DeleteServerCertificateWithContext(ctx, &iam.DeleteServerCertificateInput{ + log.Printf("[DEBUG] Deleting IAM Server Certificate: %s", d.Id()) + _, err := tfresource.RetryWhenAWSErrMessageContains(ctx, 15*time.Minute, func() (interface{}, error) { + return conn.DeleteServerCertificateWithContext(ctx, &iam.DeleteServerCertificateInput{ ServerCertificateName: aws.String(d.Get("name").(string)), }) + }, iam.ErrCodeDeleteConflictException, "currently in use by arn") + + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return diags } if err != nil { @@ -289,27 +255,35 @@ func resourceServerCertificateDelete(ctx context.Context, d *schema.ResourceData return diags } -func resourceServerCertificateImport(ctx context.Context, - d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { +func resourceServerCertificateImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { d.Set("name", d.Id()) // private_key can't be fetched from any API call return []*schema.ResourceData{d}, nil } -func currentlyInUseBy(ctx context.Context, awsErr string, conn *elb.ELB) { - r := regexp.MustCompile(`currently in use by ([a-z0-9:-]+)\/([a-z0-9-]+)\.`) - matches := r.FindStringSubmatch(awsErr) - if len(matches) > 0 { - lbName := matches[2] - describeElbOpts := &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(lbName)}, - } - if _, err := conn.DescribeLoadBalancersWithContext(ctx, describeElbOpts); err != nil { - if tfawserr.ErrCodeEquals(err, "LoadBalancerNotFound") { - log.Printf("[WARN] Load Balancer (%s) causing delete conflict not found", lbName) - } +func FindServerCertificateByName(ctx context.Context, conn *iam.IAM, name string) (*iam.ServerCertificate, error) { + input := &iam.GetServerCertificateInput{ + ServerCertificateName: aws.String(name), + } + + output, err := conn.GetServerCertificateWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, iam.ErrCodeNoSuchEntityException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, } } + + if err != nil { + return nil, err + } + + if output == nil || output.ServerCertificate == nil || output.ServerCertificate.ServerCertificateMetadata == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ServerCertificate, nil } func normalizeCert(cert interface{}) string { diff --git a/internal/service/iam/server_certificate_test.go b/internal/service/iam/server_certificate_test.go index af9348bb7c8..24be9a5e45d 100644 --- a/internal/service/iam/server_certificate_test.go +++ b/internal/service/iam/server_certificate_test.go @@ -6,7 +6,6 @@ import ( "strings" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/iam" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -14,15 +13,14 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfiam "github.com/hashicorp/terraform-provider-aws/internal/service/iam" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccIAMServerCertificate_basic(t *testing.T) { ctx := acctest.Context(t) var cert iam.ServerCertificate - resourceName := "aws_iam_server_certificate.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - key := acctest.TLSRSAPrivateKeyPEM(t, 2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") @@ -41,6 +39,7 @@ func TestAccIAMServerCertificate_basic(t *testing.T) { acctest.CheckResourceAttrRFC3339(resourceName, "upload_date"), resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "name_prefix", ""), resource.TestCheckResourceAttr(resourceName, "path", "/"), resource.TestCheckResourceAttr(resourceName, "certificate_body", strings.TrimSpace(certificate)), ), @@ -56,13 +55,10 @@ func TestAccIAMServerCertificate_basic(t *testing.T) { }) } -func TestAccIAMServerCertificate_tags(t *testing.T) { +func TestAccIAMServerCertificate_nameGenerated(t *testing.T) { ctx := acctest.Context(t) var cert iam.ServerCertificate - resourceName := "aws_iam_server_certificate.test" - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - key := acctest.TLSRSAPrivateKeyPEM(t, 2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") @@ -73,47 +69,47 @@ func TestAccIAMServerCertificate_tags(t *testing.T) { CheckDestroy: testAccCheckServerCertificateDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccServerCertificateConfig_tags1(rName, key, certificate, "key1", "value1"), - Check: resource.ComposeTestCheckFunc( - testAccCheckCertExists(ctx, resourceName, &cert), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), - ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, - ImportStateId: rName, - ImportStateVerifyIgnore: []string{"private_key"}, - }, - { - Config: testAccServerCertificateConfig_tags2(rName, key, certificate, "key1", "value1updated", "key2", "value2"), + Config: testAccServerCertificateConfig_nameGenerated(key, certificate), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists(ctx, resourceName, &cert), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), - resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + acctest.CheckResourceAttrNameGenerated(resourceName, "name"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", resource.UniqueIdPrefix), ), }, + }, + }) +} + +func TestAccIAMServerCertificate_namePrefix(t *testing.T) { + ctx := acctest.Context(t) + var cert iam.ServerCertificate + resourceName := "aws_iam_server_certificate.test" + key := acctest.TLSRSAPrivateKeyPEM(t, 2048) + certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, iam.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckServerCertificateDestroy(ctx), + Steps: []resource.TestStep{ { - Config: testAccServerCertificateConfig_tags1(rName, key, certificate, "key2", "value2"), + Config: testAccServerCertificateConfig_namePrefix("tf-acc-test-prefix-", key, certificate), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists(ctx, resourceName, &cert), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + acctest.CheckResourceAttrNameFromPrefix(resourceName, "name", "tf-acc-test-prefix-"), + resource.TestCheckResourceAttr(resourceName, "name_prefix", "tf-acc-test-prefix-"), ), }, }, }) } -func TestAccIAMServerCertificate_Name_prefix(t *testing.T) { +func TestAccIAMServerCertificate_disappears(t *testing.T) { ctx := acctest.Context(t) var cert iam.ServerCertificate - resourceName := "aws_iam_server_certificate.test" - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) key := acctest.TLSRSAPrivateKeyPEM(t, 2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") @@ -124,20 +120,22 @@ func TestAccIAMServerCertificate_Name_prefix(t *testing.T) { CheckDestroy: testAccCheckServerCertificateDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccServerCertificateConfig_random(key, certificate), + Config: testAccServerCertificateConfig_basic(rName, key, certificate), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists(ctx, resourceName, &cert), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfiam.ResourceServerCertificate(), resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) } -func TestAccIAMServerCertificate_disappears(t *testing.T) { +func TestAccIAMServerCertificate_tags(t *testing.T) { ctx := acctest.Context(t) var cert iam.ServerCertificate resourceName := "aws_iam_server_certificate.test" - + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) key := acctest.TLSRSAPrivateKeyPEM(t, 2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") @@ -148,12 +146,36 @@ func TestAccIAMServerCertificate_disappears(t *testing.T) { CheckDestroy: testAccCheckServerCertificateDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccServerCertificateConfig_random(key, certificate), + Config: testAccServerCertificateConfig_tags1(rName, key, certificate, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists(ctx, resourceName, &cert), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfiam.ResourceServerCertificate(), resourceName), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateId: rName, + ImportStateVerifyIgnore: []string{"private_key"}, + }, + { + Config: testAccServerCertificateConfig_tags2(rName, key, certificate, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCertExists(ctx, resourceName, &cert), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccServerCertificateConfig_tags1(rName, key, certificate, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckCertExists(ctx, resourceName, &cert), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), - ExpectNonEmptyPlan: true, }, }, }) @@ -162,12 +184,10 @@ func TestAccIAMServerCertificate_disappears(t *testing.T) { func TestAccIAMServerCertificate_file(t *testing.T) { ctx := acctest.Context(t) var cert iam.ServerCertificate - - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) unixFile := "test-fixtures/iam-ssl-unix-line-endings.pem" winFile := "test-fixtures/iam-ssl-windows-line-endings.pem.winfile" resourceName := "aws_iam_server_certificate.test" - resourceId := fmt.Sprintf("terraform-test-cert-%d", rInt) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -176,7 +196,7 @@ func TestAccIAMServerCertificate_file(t *testing.T) { CheckDestroy: testAccCheckServerCertificateDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccServerCertificateConfig_file(rInt, unixFile), + Config: testAccServerCertificateConfig_file(rName, unixFile), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists(ctx, resourceName, &cert), ), @@ -185,11 +205,11 @@ func TestAccIAMServerCertificate_file(t *testing.T) { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, - ImportStateId: resourceId, + ImportStateId: rName, ImportStateVerifyIgnore: []string{"private_key"}, }, { - Config: testAccServerCertificateConfig_file(rInt, winFile), + Config: testAccServerCertificateConfig_file(rName, winFile), Check: resource.ComposeTestCheckFunc( testAccCheckCertExists(ctx, resourceName, &cert), ), @@ -201,10 +221,8 @@ func TestAccIAMServerCertificate_file(t *testing.T) { func TestAccIAMServerCertificate_path(t *testing.T) { ctx := acctest.Context(t) var cert iam.ServerCertificate - resourceName := "aws_iam_server_certificate.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) - key := acctest.TLSRSAPrivateKeyPEM(t, 2048) certificate := acctest.TLSRSAX509SelfSignedCertificatePEM(t, key, "example.com") @@ -232,7 +250,7 @@ func TestAccIAMServerCertificate_path(t *testing.T) { }) } -func testAccCheckCertExists(ctx context.Context, n string, cert *iam.ServerCertificate) resource.TestCheckFunc { +func testAccCheckCertExists(ctx context.Context, n string, v *iam.ServerCertificate) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -240,19 +258,18 @@ func testAccCheckCertExists(ctx context.Context, n string, cert *iam.ServerCerti } if rs.Primary.ID == "" { - return fmt.Errorf("No Server Cert ID is set") + return fmt.Errorf("No IAM Server Certificate ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).IAMConn() - describeOpts := &iam.GetServerCertificateInput{ - ServerCertificateName: aws.String(rs.Primary.Attributes["name"]), - } - resp, err := conn.GetServerCertificateWithContext(ctx, describeOpts) + + output, err := tfiam.FindServerCertificateByName(ctx, conn, rs.Primary.Attributes["name"]) + if err != nil { return err } - *cert = *resp.ServerCertificate + *v = *output return nil } @@ -267,18 +284,17 @@ func testAccCheckServerCertificateDestroy(ctx context.Context) resource.TestChec continue } - // Try to find the Cert - opts := &iam.GetServerCertificateInput{ - ServerCertificateName: aws.String(rs.Primary.Attributes["name"]), + _, err := tfiam.FindServerCertificateByName(ctx, conn, rs.Primary.Attributes["name"]) + + if tfresource.NotFound(err) { + continue } - resp, err := conn.GetServerCertificateWithContext(ctx, opts) - if err == nil { - if resp.ServerCertificate != nil { - return fmt.Errorf("Error: Server Cert still exists") - } - return nil + if err != nil { + return err } + + return fmt.Errorf("IAM Server Certificate %s still exists", rs.Primary.ID) } return nil @@ -288,28 +304,37 @@ func testAccCheckServerCertificateDestroy(ctx context.Context) resource.TestChec func testAccServerCertificateConfig_basic(rName, key, certificate string) string { return fmt.Sprintf(` resource "aws_iam_server_certificate" "test" { - name = "%[1]s" + name = %[1]q certificate_body = "%[2]s" private_key = "%[3]s" } `, rName, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key)) } -func testAccServerCertificateConfig_random(key, certificate string) string { +func testAccServerCertificateConfig_nameGenerated(key, certificate string) string { return fmt.Sprintf(` resource "aws_iam_server_certificate" "test" { - name_prefix = "tf-acc-test" certificate_body = "%[1]s" private_key = "%[2]s" } `, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key)) } +func testAccServerCertificateConfig_namePrefix(namePrefix, key, certificate string) string { + return fmt.Sprintf(` +resource "aws_iam_server_certificate" "test" { + name_prefix = %[1]q + certificate_body = "%[2]s" + private_key = "%[3]s" +} +`, namePrefix, acctest.TLSPEMEscapeNewlines(certificate), acctest.TLSPEMEscapeNewlines(key)) +} + func testAccServerCertificateConfig_path(rName, path, key, certificate string) string { return fmt.Sprintf(` resource "aws_iam_server_certificate" "test" { - name = "%[1]s" - path = "%[2]s" + name = %[1]q + path = %[2]q certificate_body = "%[3]s" private_key = "%[4]s" } @@ -317,11 +342,11 @@ resource "aws_iam_server_certificate" "test" { } // iam-ssl-unix-line-endings -func testAccServerCertificateConfig_file(rInt int, fName string) string { +func testAccServerCertificateConfig_file(rName, fName string) string { return fmt.Sprintf(` resource "aws_iam_server_certificate" "test" { - name = "terraform-test-cert-%d" - certificate_body = file("%s") + name = %[1]q + certificate_body = file(%[2]q) private_key = < 0 { - return fmt.Errorf("still exist.") - } + _, err := tfneptune.FindSubnetGroupByName(ctx, conn, rs.Primary.ID) - return nil + if tfresource.NotFound(err) { + continue } - // Verify the error is what we want - neptuneerr, ok := err.(awserr.Error) - if !ok { - return err - } - if neptuneerr.Code() != "DBSubnetGroupNotFoundFault" { + if err != nil { return err } + + return fmt.Errorf("Neptune Subnet Group %s still exists", rs.Primary.ID) } return nil @@ -178,176 +243,80 @@ func testAccCheckSubnetGroupExists(ctx context.Context, n string, v *neptune.DBS } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") + return fmt.Errorf("No Neptune Subnet Group ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).NeptuneConn() - resp, err := conn.DescribeDBSubnetGroupsWithContext(ctx, &neptune.DescribeDBSubnetGroupsInput{DBSubnetGroupName: aws.String(rs.Primary.ID)}) + + output, err := tfneptune.FindSubnetGroupByName(ctx, conn, rs.Primary.ID) + if err != nil { return err } - if len(resp.DBSubnetGroups) == 0 { - return fmt.Errorf("DbSubnetGroup not found") - } - *v = *resp.DBSubnetGroups[0] + *v = *output return nil } } func testAccSubnetGroupConfig_basic(rName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = "terraform-testacc-neptune-subnet-group" - } -} - -resource "aws_subnet" "foo" { - cidr_block = "10.1.1.0/24" - availability_zone = data.aws_availability_zones.available.names[0] - vpc_id = aws_vpc.foo.id - - tags = { - Name = "tf-acc-neptune-subnet-group-1" - } -} - -resource "aws_subnet" "bar" { - cidr_block = "10.1.2.0/24" - availability_zone = data.aws_availability_zones.available.names[1] - vpc_id = aws_vpc.foo.id - - tags = { - Name = "tf-acc-neptune-subnet-group-2" - } -} - -resource "aws_neptune_subnet_group" "foo" { - name = "%s" - subnet_ids = [aws_subnet.foo.id, aws_subnet.bar.id] - - tags = { - Name = "tf-neptunesubnet-group-test" - } + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` +resource "aws_neptune_subnet_group" "test" { + name = %[1]q + subnet_ids = aws_subnet.test[*].id } `, rName)) } -func testAccSubnetGroupConfig_updatedDescription(rName string) string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), fmt.Sprintf(` -resource "aws_vpc" "foo" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = "terraform-testacc-neptune-subnet-group-updated-description" - } -} - -resource "aws_subnet" "foo" { - cidr_block = "10.1.1.0/24" - availability_zone = data.aws_availability_zones.available.names[0] - vpc_id = aws_vpc.foo.id - - tags = { - Name = "tf-acc-neptune-subnet-group-1" - } -} - -resource "aws_subnet" "bar" { - cidr_block = "10.1.2.0/24" - availability_zone = data.aws_availability_zones.available.names[1] - vpc_id = aws_vpc.foo.id - - tags = { - Name = "tf-acc-neptune-subnet-group-2" - } -} - -resource "aws_neptune_subnet_group" "foo" { - name = "%s" - description = "foo description updated" - subnet_ids = [aws_subnet.foo.id, aws_subnet.bar.id] - - tags = { - Name = "tf-neptunesubnet-group-test" - } -} -`, rName)) +func testAccSubnetGroupConfig_nameGenerated(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), ` +resource "aws_neptune_subnet_group" "test" { + subnet_ids = aws_subnet.test[*].id +}`) } -func testAccSubnetGroupConfig_namePrefix() string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), ` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" - - tags = { - Name = "terraform-testacc-neptune-subnet-group-name-prefix" - } +func testAccSubnetGroupConfig_namePrefix(rName, prefix string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` +resource "aws_neptune_subnet_group" "test" { + name_prefix = %[1]q + subnet_ids = aws_subnet.test[*].id +}`, prefix)) } -resource "aws_subnet" "a" { - vpc_id = aws_vpc.test.id - cidr_block = "10.1.1.0/24" - availability_zone = data.aws_availability_zones.available.names[0] +func testAccSubnetGroupConfig_tags1(rName, tagKey1, tagValue1 string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` +resource "aws_neptune_subnet_group" "test" { + name = %[1]q + subnet_ids = aws_subnet.test[*].id tags = { - Name = "tf-acc-neptune-subnet-group-name-prefix-a" + %[2]q = %[3]q } } - -resource "aws_subnet" "b" { - vpc_id = aws_vpc.test.id - cidr_block = "10.1.2.0/24" - availability_zone = data.aws_availability_zones.available.names[1] - - tags = { - Name = "tf-acc-neptune-subnet-group-name-prefix-b" - } +`, rName, tagKey1, tagValue1)) } +func testAccSubnetGroupConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 2), fmt.Sprintf(` resource "aws_neptune_subnet_group" "test" { - name_prefix = "tf_test-" - subnet_ids = [aws_subnet.a.id, aws_subnet.b.id] -} -`) -} - -func testAccSubnetGroupConfig_generatedName() string { - return acctest.ConfigCompose(acctest.ConfigAvailableAZsNoOptIn(), ` -resource "aws_vpc" "test" { - cidr_block = "10.1.0.0/16" + name = %[1]q + subnet_ids = aws_subnet.test[*].id tags = { - Name = "terraform-testacc-neptune-subnet-group-generated-name" + %[2]q = %[3]q + %[4]q = %[5]q } } - -resource "aws_subnet" "a" { - vpc_id = aws_vpc.test.id - cidr_block = "10.1.1.0/24" - availability_zone = data.aws_availability_zones.available.names[0] - - tags = { - Name = "tf-acc-neptune-subnet-group-generated-name-a" - } -} - -resource "aws_subnet" "b" { - vpc_id = aws_vpc.test.id - cidr_block = "10.1.2.0/24" - availability_zone = data.aws_availability_zones.available.names[1] - - tags = { - Name = "tf-acc-neptune-subnet-group-generated-name-a" - } +`, rName, tagKey1, tagValue1, tagKey2, tagValue2)) } +func testAccSubnetGroupConfig_update(rName string) string { + return acctest.ConfigCompose(acctest.ConfigVPCWithSubnets(rName, 3), fmt.Sprintf(` resource "aws_neptune_subnet_group" "test" { - subnet_ids = [aws_subnet.a.id, aws_subnet.b.id] + name = %[1]q + subnet_ids = aws_subnet.test[*].id + description = "test description updated" } -`) +`, rName)) } diff --git a/internal/service/opensearch/domain_test.go b/internal/service/opensearch/domain_test.go index a590d8a9687..de62b95cd2d 100644 --- a/internal/service/opensearch/domain_test.go +++ b/internal/service/opensearch/domain_test.go @@ -8,9 +8,7 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" - "github.com/aws/aws-sdk-go/service/elb" "github.com/aws/aws-sdk-go/service/opensearchservice" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -1493,7 +1491,7 @@ func TestAccOpenSearchDomain_tags(t *testing.T) { PreCheck: func() { acctest.PreCheck(t); testAccPreCheckIAMServiceLinkedRole(t) }, ErrorCheck: acctest.ErrorCheck(t, opensearchservice.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckELBDestroy(ctx), + CheckDestroy: testAccCheckDomainDestroy(ctx), Steps: []resource.TestStep{ { Config: testAccDomainConfig_tags1(rName, "key1", "value1"), @@ -2034,41 +2032,6 @@ func testAccPreCheckCognitoIdentityProvider(ctx context.Context, t *testing.T) { } } -func testAccCheckELBDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ELBConn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_elb" { - continue - } - - describe, err := conn.DescribeLoadBalancersWithContext(ctx, &elb.DescribeLoadBalancersInput{ - LoadBalancerNames: []*string{aws.String(rs.Primary.ID)}, - }) - - if err == nil { - if len(describe.LoadBalancerDescriptions) != 0 && - *describe.LoadBalancerDescriptions[0].LoadBalancerName == rs.Primary.ID { - return fmt.Errorf("ELB still exists") - } - } - - // Verify the error - providerErr, ok := err.(awserr.Error) - if !ok { - return err - } - - if providerErr.Code() != elb.ErrCodeAccessPointNotFoundException { - return fmt.Errorf("Unexpected error: %s", err) - } - } - - return nil - } -} - func testAccDomainConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_opensearch_domain" "test" { diff --git a/internal/service/opsworks/instance.go b/internal/service/opsworks/instance.go index 835cf60a348..e0f0980ee76 100644 --- a/internal/service/opsworks/instance.go +++ b/internal/service/opsworks/instance.go @@ -11,6 +11,7 @@ import ( "github.com/aws/aws-sdk-go/service/opsworks" "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" @@ -904,6 +905,71 @@ func stopInstance(ctx context.Context, d *schema.ResourceData, meta interface{}, return nil } +func waitInstanceDeleted(ctx context.Context, conn *opsworks.OpsWorks, instanceId string) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{instanceStatusStopped, instanceStatusTerminating, instanceStatusTerminated}, + Target: []string{}, + Refresh: instanceStatus(ctx, conn, instanceId), + Timeout: 2 * time.Minute, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + + _, err := stateConf.WaitForStateContext(ctx) + return err +} + +func waitInstanceStarted(ctx context.Context, conn *opsworks.OpsWorks, instanceId string, timeout time.Duration) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{instanceStatusRequested, instanceStatusPending, instanceStatusBooting, instanceStatusRunningSetup}, + Target: []string{instanceStatusOnline}, + Refresh: instanceStatus(ctx, conn, instanceId), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err := stateConf.WaitForStateContext(ctx) + return err +} + +func waitInstanceStopped(ctx context.Context, conn *opsworks.OpsWorks, instanceId string, timeout time.Duration) error { + stateConf := &resource.StateChangeConf{ + Pending: []string{instanceStatusStopping, instanceStatusTerminating, instanceStatusShuttingDown, instanceStatusTerminated}, + Target: []string{instanceStatusStopped}, + Refresh: instanceStatus(ctx, conn, instanceId), + Timeout: timeout, + Delay: 10 * time.Second, + MinTimeout: 3 * time.Second, + } + _, err := stateConf.WaitForStateContext(ctx) + return err +} + +func instanceStatus(ctx context.Context, conn *opsworks.OpsWorks, instanceID string) resource.StateRefreshFunc { + return func() (interface{}, string, error) { + resp, err := conn.DescribeInstancesWithContext(ctx, &opsworks.DescribeInstancesInput{ + InstanceIds: []*string{aws.String(instanceID)}, + }) + + if tfawserr.ErrCodeEquals(err, opsworks.ErrCodeResourceNotFoundException) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + if resp == nil || len(resp.Instances) == 0 || resp.Instances[0] == nil { + // Sometimes AWS just has consistency issues and doesn't see + // our instance yet. Return an empty state. + return nil, "", nil + } + + i := resp.Instances[0] + return i, aws.StringValue(i.Status), nil + } +} + func readBlockDevices(instance *opsworks.Instance) map[string]interface{} { blockDevices := make(map[string]interface{}) blockDevices["ebs"] = make([]map[string]interface{}, 0) diff --git a/internal/service/opsworks/permission.go b/internal/service/opsworks/permission.go index f34a4c8796a..822e362c8a6 100644 --- a/internal/service/opsworks/permission.go +++ b/internal/service/opsworks/permission.go @@ -5,7 +5,6 @@ import ( "log" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/opsworks" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -20,9 +19,9 @@ import ( func ResourcePermission() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceSetPermission, - UpdateWithoutTimeout: resourceSetPermission, - DeleteWithoutTimeout: resourcePermissionDelete, ReadWithoutTimeout: resourcePermissionRead, + UpdateWithoutTimeout: resourceSetPermission, + DeleteWithoutTimeout: schema.NoopContext, Schema: map[string]*schema.Schema{ "allow_ssh": { @@ -35,10 +34,6 @@ func ResourcePermission() *schema.Resource { Computed: true, Optional: true, }, - "user_arn": { - Type: schema.TypeString, - Required: true, - }, "level": { Type: schema.TypeString, Computed: true, @@ -53,98 +48,106 @@ func ResourcePermission() *schema.Resource { }, "stack_id": { Type: schema.TypeString, - Computed: true, - Optional: true, + Required: true, + ForceNew: true, + }, + "user_arn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, }, }, } } -func resourcePermissionDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceSetPermission(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - return diags -} + conn := meta.(*conns.AWSClient).OpsWorksConn() -func resourcePermissionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - client := meta.(*conns.AWSClient).OpsWorksConn() + iamUserARN := d.Get("user_arn").(string) + stackID := d.Get("stack_id").(string) + id := iamUserARN + stackID + input := &opsworks.SetPermissionInput{ + AllowSudo: aws.Bool(d.Get("allow_sudo").(bool)), + AllowSsh: aws.Bool(d.Get("allow_ssh").(bool)), + IamUserArn: aws.String(iamUserARN), + StackId: aws.String(stackID), + } - req := &opsworks.DescribePermissionsInput{ - IamUserArn: aws.String(d.Get("user_arn").(string)), - StackId: aws.String(d.Get("stack_id").(string)), + if d.IsNewResource() { + if v, ok := d.GetOk("level"); ok { + input.Level = aws.String(v.(string)) + } + } else if d.HasChange("level") { + input.Level = aws.String(d.Get("level").(string)) } - log.Printf("[DEBUG] Reading OpsWorks prermissions for: %s on stack: %s", d.Get("user_arn"), d.Get("stack_id")) + _, err := tfresource.RetryWhenAWSErrMessageContains(ctx, propagationTimeout, func() (interface{}, error) { + return conn.SetPermissionWithContext(ctx, input) + }, opsworks.ErrCodeResourceNotFoundException, "Unable to find user with ARN") - resp, err := client.DescribePermissionsWithContext(ctx, req) if err != nil { - if awserr, ok := err.(awserr.Error); ok { - if awserr.Code() == "ResourceNotFoundException" { - log.Printf("[INFO] Permission not found") - d.SetId("") - return diags - } - } - return sdkdiag.AppendErrorf(diags, "reading OpsWorks Permissions (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting OpsWorks Permission (%s): %s", id, err) } - found := false - id := "" - for _, permission := range resp.Permissions { - id = *permission.IamUserArn + *permission.StackId - - if d.Get("user_arn").(string)+d.Get("stack_id").(string) == id { - found = true - d.SetId(id) - d.Set("allow_ssh", permission.AllowSsh) - d.Set("allow_sudo", permission.AllowSudo) - d.Set("user_arn", permission.IamUserArn) - d.Set("stack_id", permission.StackId) - d.Set("level", permission.Level) - } + if d.IsNewResource() { + d.SetId(id) } - if !found { + return append(diags, resourcePermissionRead(ctx, d, meta)...) +} + +func resourcePermissionRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).OpsWorksConn() + + permission, err := FindPermissionByTwoPartKey(ctx, conn, d.Get("user_arn").(string), d.Get("stack_id").(string)) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] OpsWorks Permission %s not found, removing from state", d.Id()) d.SetId("") - log.Printf("[INFO] The correct permission could not be found for: %s on stack: %s", d.Get("user_arn"), d.Get("stack_id")) + return diags + } + + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading OpsWorks Permission (%s): %s", d.Id(), err) } + d.Set("allow_ssh", permission.AllowSsh) + d.Set("allow_sudo", permission.AllowSudo) + d.Set("level", permission.Level) + d.Set("stack_id", permission.StackId) + d.Set("user_arn", permission.IamUserArn) + return diags } -func resourceSetPermission(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - client := meta.(*conns.AWSClient).OpsWorksConn() - - req := &opsworks.SetPermissionInput{ - AllowSudo: aws.Bool(d.Get("allow_sudo").(bool)), - AllowSsh: aws.Bool(d.Get("allow_ssh").(bool)), - IamUserArn: aws.String(d.Get("user_arn").(string)), - StackId: aws.String(d.Get("stack_id").(string)), +func FindPermissionByTwoPartKey(ctx context.Context, conn *opsworks.OpsWorks, iamUserARN, stackID string) (*opsworks.Permission, error) { + input := &opsworks.DescribePermissionsInput{ + IamUserArn: aws.String(iamUserARN), + StackId: aws.String(stackID), } - if d.HasChange("level") { - req.Level = aws.String(d.Get("level").(string)) - } + output, err := conn.DescribePermissionsWithContext(ctx, input) - err := resource.RetryContext(ctx, propagationTimeout, func() *resource.RetryError { - _, err := client.SetPermissionWithContext(ctx, req) - if err != nil { - if tfawserr.ErrMessageContains(err, opsworks.ErrCodeResourceNotFoundException, "Unable to find user with ARN") { - return resource.RetryableError(err) - } - return resource.NonRetryableError(err) + if tfawserr.ErrCodeEquals(err, opsworks.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, } - return nil - }) - - if tfresource.TimedOut(err) { - _, err = client.SetPermissionWithContext(ctx, req) } if err != nil { - return sdkdiag.AppendErrorf(diags, "setting OpsWorks Permissions (%s): %s", d.Id(), err) + return nil, err } - return append(diags, resourcePermissionRead(ctx, d, meta)...) + if output == nil || len(output.Permissions) == 0 || output.Permissions[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + if count := len(output.Permissions); count > 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.Permissions[0], nil } diff --git a/internal/service/opsworks/permission_test.go b/internal/service/opsworks/permission_test.go index 832fc02e204..08091f65002 100644 --- a/internal/service/opsworks/permission_test.go +++ b/internal/service/opsworks/permission_test.go @@ -5,14 +5,13 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/opsworks" 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" + tfopsworks "github.com/hashicorp/terraform-provider-aws/internal/service/opsworks" ) func TestAccOpsWorksPermission_basic(t *testing.T) { @@ -20,17 +19,17 @@ func TestAccOpsWorksPermission_basic(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_opsworks_permission.test" var opsperm opsworks.Permission + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(opsworks.EndpointsID, t) }, ErrorCheck: acctest.ErrorCheck(t, opsworks.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckPermissionDestroy(ctx), + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccPermissionConfig_create(rName, true, true, "iam_only"), Check: resource.ComposeTestCheckFunc( testAccCheckPermissionExists(ctx, resourceName, &opsperm), - testAccCheckCreatePermissionAttributes(&opsperm, true, true, "iam_only"), resource.TestCheckResourceAttr(resourceName, "allow_ssh", "true"), resource.TestCheckResourceAttr(resourceName, "allow_sudo", "true"), resource.TestCheckResourceAttr(resourceName, "level", "iam_only"), @@ -40,7 +39,6 @@ func TestAccOpsWorksPermission_basic(t *testing.T) { Config: testAccPermissionConfig_create(rName, true, false, "iam_only"), Check: resource.ComposeTestCheckFunc( testAccCheckPermissionExists(ctx, resourceName, &opsperm), - testAccCheckCreatePermissionAttributes(&opsperm, true, false, "iam_only"), resource.TestCheckResourceAttr(resourceName, "allow_ssh", "true"), resource.TestCheckResourceAttr(resourceName, "allow_sudo", "false"), resource.TestCheckResourceAttr(resourceName, "level", "iam_only"), @@ -50,7 +48,6 @@ func TestAccOpsWorksPermission_basic(t *testing.T) { Config: testAccPermissionConfig_create(rName, false, false, "deny"), Check: resource.ComposeTestCheckFunc( testAccCheckPermissionExists(ctx, resourceName, &opsperm), - testAccCheckCreatePermissionAttributes(&opsperm, false, false, "deny"), resource.TestCheckResourceAttr(resourceName, "allow_ssh", "false"), resource.TestCheckResourceAttr(resourceName, "allow_sudo", "false"), resource.TestCheckResourceAttr(resourceName, "level", "deny"), @@ -60,7 +57,6 @@ func TestAccOpsWorksPermission_basic(t *testing.T) { Config: testAccPermissionConfig_create(rName, false, false, "show"), Check: resource.ComposeTestCheckFunc( testAccCheckPermissionExists(ctx, resourceName, &opsperm), - testAccCheckCreatePermissionAttributes(&opsperm, false, false, "show"), resource.TestCheckResourceAttr(resourceName, "allow_ssh", "false"), resource.TestCheckResourceAttr(resourceName, "allow_sudo", "false"), resource.TestCheckResourceAttr(resourceName, "level", "show"), @@ -81,7 +77,7 @@ func TestAccOpsWorksPermission_self(t *testing.T) { PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(opsworks.EndpointsID, t) }, ErrorCheck: acctest.ErrorCheck(t, opsworks.EndpointsID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: nil, // Cannot delete own OpsWorks Permission + CheckDestroy: acctest.CheckDestroyNoop, Steps: []resource.TestStep{ { Config: testAccPermissionConfig_self(rName, true, true), @@ -103,8 +99,7 @@ func TestAccOpsWorksPermission_self(t *testing.T) { }) } -func testAccCheckPermissionExists(ctx context.Context, - n string, opsperm *opsworks.Permission) resource.TestCheckFunc { +func testAccCheckPermissionExists(ctx context.Context, n string, v *opsworks.Permission) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -112,193 +107,23 @@ func testAccCheckPermissionExists(ctx context.Context, } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") + return fmt.Errorf("No OpsWorks Layer ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).OpsWorksConn() - params := &opsworks.DescribePermissionsInput{ - StackId: aws.String(rs.Primary.Attributes["stack_id"]), - IamUserArn: aws.String(rs.Primary.Attributes["user_arn"]), - } - resp, err := conn.DescribePermissionsWithContext(ctx, params) + output, err := tfopsworks.FindPermissionByTwoPartKey(ctx, conn, rs.Primary.Attributes["user_arn"], rs.Primary.Attributes["stack_id"]) if err != nil { return err } - if v := len(resp.Permissions); v != 1 { - return fmt.Errorf("Expected 1 response returned, got %d", v) - } - - *opsperm = *resp.Permissions[0] + *v = *output return nil } } -func testAccCheckCreatePermissionAttributes( - opsperm *opsworks.Permission, allowSSH bool, allowSudo bool, level string) resource.TestCheckFunc { - return func(s *terraform.State) error { - if *opsperm.AllowSsh != allowSSH { - return fmt.Errorf("Unnexpected allowSSH: %t", *opsperm.AllowSsh) - } - - if *opsperm.AllowSudo != allowSudo { - return fmt.Errorf("Unnexpected allowSudo: %t", *opsperm.AllowSudo) - } - - if *opsperm.Level != level { - return fmt.Errorf("Unnexpected level: %s", *opsperm.Level) - } - - return nil - } -} - -func testAccCheckPermissionDestroy(ctx context.Context) resource.TestCheckFunc { - return func(s *terraform.State) error { - client := acctest.Provider.Meta().(*conns.AWSClient).OpsWorksConn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_opsworks_permission" { - continue - } - - req := &opsworks.DescribePermissionsInput{ - IamUserArn: aws.String(rs.Primary.Attributes["user_arn"]), - } - - resp, err := client.DescribePermissionsWithContext(ctx, req) - if err == nil { - if len(resp.Permissions) > 0 { - return fmt.Errorf("OpsWorks Permissions still exist.") - } - } - - if awserr, ok := err.(awserr.Error); ok { - if awserr.Code() != "ResourceNotFoundException" { - return err - } - } - } - return nil - } -} - -func testAccPermissionBase(rName string) string { - return fmt.Sprintf(` -data "aws_region" "current" {} - -resource "aws_vpc" "test" { - cidr_block = "10.0.0.0/24" - - tags = { - Name = %[1]q - } -} - -resource "aws_subnet" "test" { - cidr_block = aws_vpc.test.cidr_block - vpc_id = aws_vpc.test.id - - tags = { - Name = %[1]q - } -} - -resource "aws_opsworks_stack" "test" { - name = %[1]q - region = data.aws_region.current.name - vpc_id = aws_vpc.test.id - default_subnet_id = aws_subnet.test.id - service_role_arn = aws_iam_role.service.arn - default_instance_profile_arn = aws_iam_instance_profile.test.arn - default_os = "Amazon Linux 2016.09" - default_root_device_type = "ebs" - - custom_json = < 1 { + return nil, tfresource.NewTooManyResultsError(count, input) + } + + return output.UserProfiles[0], nil +} diff --git a/internal/service/opsworks/user_profile_test.go b/internal/service/opsworks/user_profile_test.go index 3a2abeb8ea9..209420a2a40 100644 --- a/internal/service/opsworks/user_profile_test.go +++ b/internal/service/opsworks/user_profile_test.go @@ -5,21 +5,22 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/opsworks" 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" + tfopsworks "github.com/hashicorp/terraform-provider-aws/internal/service/opsworks" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccOpsWorksUserProfile_basic(t *testing.T) { ctx := acctest.Context(t) - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + rName1 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) rName2 := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_opsworks_user_profile.test" + resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(opsworks.EndpointsID, t) }, ErrorCheck: acctest.ErrorCheck(t, opsworks.EndpointsID), @@ -27,18 +28,18 @@ func TestAccOpsWorksUserProfile_basic(t *testing.T) { CheckDestroy: testAccCheckUserProfileDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccUserProfileConfig_create(rName), + Config: testAccUserProfileConfig_create(rName1), Check: resource.ComposeTestCheckFunc( - testAccCheckUserProfileExists(ctx, resourceName, rName), + testAccCheckUserProfileExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "ssh_public_key", ""), - resource.TestCheckResourceAttr(resourceName, "ssh_username", rName), + resource.TestCheckResourceAttr(resourceName, "ssh_username", rName1), resource.TestCheckResourceAttr(resourceName, "allow_self_management", "false"), ), }, { - Config: testAccUserProfileConfig_update(rName, rName2), + Config: testAccUserProfileConfig_update(rName1, rName2), Check: resource.ComposeTestCheckFunc( - testAccCheckUserProfileExists(ctx, resourceName, rName2), + testAccCheckUserProfileExists(ctx, resourceName), resource.TestCheckResourceAttr(resourceName, "ssh_public_key", ""), resource.TestCheckResourceAttr(resourceName, "ssh_username", rName2), resource.TestCheckResourceAttr(resourceName, "allow_self_management", "false"), @@ -48,8 +49,30 @@ func TestAccOpsWorksUserProfile_basic(t *testing.T) { }) } -func testAccCheckUserProfileExists(ctx context.Context, - n, username string) resource.TestCheckFunc { +func TestAccOpsWorksUserProfile_disappears(t *testing.T) { + ctx := acctest.Context(t) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_opsworks_user_profile.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t); acctest.PreCheckPartitionHasService(opsworks.EndpointsID, t) }, + ErrorCheck: acctest.ErrorCheck(t, opsworks.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckUserProfileDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccUserProfileConfig_create(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckUserProfileExists(ctx, resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfopsworks.ResourceUserProfile(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func testAccCheckUserProfileExists(ctx context.Context, n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { @@ -57,69 +80,39 @@ func testAccCheckUserProfileExists(ctx context.Context, } if rs.Primary.ID == "" { - return fmt.Errorf("No ID is set") - } - - if _, ok := rs.Primary.Attributes["user_arn"]; !ok { - return fmt.Errorf("User Profile user arn is missing, should be set.") + return fmt.Errorf("No OpsWorks User Profile ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).OpsWorksConn() - params := &opsworks.DescribeUserProfilesInput{ - IamUserArns: []*string{aws.String(rs.Primary.Attributes["user_arn"])}, - } - resp, err := conn.DescribeUserProfilesWithContext(ctx, params) - - if err != nil { - return err - } - - if v := len(resp.UserProfiles); v != 1 { - return fmt.Errorf("Expected 1 response returned, got %d", v) - } - - opsuserprofile := *resp.UserProfiles[0] + _, err := tfopsworks.FindUserProfileByARN(ctx, conn, rs.Primary.ID) - if *opsuserprofile.AllowSelfManagement { - return fmt.Errorf("Unnexpected allowSelfManagement: %t", - *opsuserprofile.AllowSelfManagement) - } - - if *opsuserprofile.Name != username { - return fmt.Errorf("Unnexpected name: %s", *opsuserprofile.Name) - } - - return nil + return err } } func testAccCheckUserProfileDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - client := acctest.Provider.Meta().(*conns.AWSClient).OpsWorksConn() + conn := acctest.Provider.Meta().(*conns.AWSClient).OpsWorksConn() for _, rs := range s.RootModule().Resources { if rs.Type != "aws_opsworks_user_profile" { continue } - req := &opsworks.DescribeUserProfilesInput{ - IamUserArns: []*string{aws.String(rs.Primary.Attributes["user_arn"])}, - } - resp, err := client.DescribeUserProfilesWithContext(ctx, req) + _, err := tfopsworks.FindUserProfileByARN(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.UserProfiles) > 0 { - return fmt.Errorf("OpsWorks User Profiles still exist.") - } + if tfresource.NotFound(err) { + continue } - if awserr, ok := err.(awserr.Error); ok { - if awserr.Code() != "ResourceNotFoundException" { - return err - } + if err != nil { + return err } + + return fmt.Errorf("OpsWorks User Profile %s still exists", rs.Primary.ID) } + return nil } } @@ -127,32 +120,32 @@ func testAccCheckUserProfileDestroy(ctx context.Context) resource.TestCheckFunc func testAccUserProfileConfig_create(rName string) string { return fmt.Sprintf(` resource "aws_opsworks_user_profile" "test" { - user_arn = aws_iam_user.test.arn - ssh_username = aws_iam_user.test.name + user_arn = aws_iam_user.test1.arn + ssh_username = aws_iam_user.test1.name } -resource "aws_iam_user" "test" { +resource "aws_iam_user" "test1" { name = %[1]q path = "/" } `, rName) } -func testAccUserProfileConfig_update(rName, rName2 string) string { +func testAccUserProfileConfig_update(rName1, rName2 string) string { return fmt.Sprintf(` resource "aws_opsworks_user_profile" "test" { - user_arn = aws_iam_user.new-test.arn - ssh_username = aws_iam_user.new-test.name + user_arn = aws_iam_user.test2.arn + ssh_username = aws_iam_user.test2.name } -resource "aws_iam_user" "test" { +resource "aws_iam_user" "test1" { name = %[1]q path = "/" } -resource "aws_iam_user" "new-test" { +resource "aws_iam_user" "test2" { name = %[2]q path = "/" } -`, rName, rName2) +`, rName1, rName2) } diff --git a/internal/service/opsworks/wait.go b/internal/service/opsworks/wait.go deleted file mode 100644 index 979aff73a92..00000000000 --- a/internal/service/opsworks/wait.go +++ /dev/null @@ -1,53 +0,0 @@ -package opsworks - -import ( - "context" - "time" - - "github.com/aws/aws-sdk-go/service/opsworks" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -const ( - InstanceDeleteTimeout = 2 * time.Minute -) - -func waitInstanceDeleted(ctx context.Context, conn *opsworks.OpsWorks, instanceId string) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{instanceStatusStopped, instanceStatusTerminating, instanceStatusTerminated}, - Target: []string{}, - Refresh: InstanceStatus(ctx, conn, instanceId), - Timeout: InstanceDeleteTimeout, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } - - _, err := stateConf.WaitForStateContext(ctx) - return err -} - -func waitInstanceStarted(ctx context.Context, conn *opsworks.OpsWorks, instanceId string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{instanceStatusRequested, instanceStatusPending, instanceStatusBooting, instanceStatusRunningSetup}, - Target: []string{instanceStatusOnline}, - Refresh: InstanceStatus(ctx, conn, instanceId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } - _, err := stateConf.WaitForStateContext(ctx) - return err -} - -func waitInstanceStopped(ctx context.Context, conn *opsworks.OpsWorks, instanceId string, timeout time.Duration) error { - stateConf := &resource.StateChangeConf{ - Pending: []string{instanceStatusStopping, instanceStatusTerminating, instanceStatusShuttingDown, instanceStatusTerminated}, - Target: []string{instanceStatusStopped}, - Refresh: InstanceStatus(ctx, conn, instanceId), - Timeout: timeout, - Delay: 10 * time.Second, - MinTimeout: 3 * time.Second, - } - _, err := stateConf.WaitForStateContext(ctx) - return err -} diff --git a/internal/service/rds/cluster_parameter_group.go b/internal/service/rds/cluster_parameter_group.go index f4dc2ee69a0..0b8826353fa 100644 --- a/internal/service/rds/cluster_parameter_group.go +++ b/internal/service/rds/cluster_parameter_group.go @@ -8,7 +8,6 @@ import ( rds_sdkv2 "github.com/aws/aws-sdk-go-v2/service/rds" "github.com/aws/aws-sdk-go-v2/service/rds/types" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/rds" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -31,6 +30,7 @@ func ResourceClusterParameterGroup() *schema.Resource { ReadWithoutTimeout: resourceClusterParameterGroupRead, UpdateWithoutTimeout: resourceClusterParameterGroupUpdate, DeleteWithoutTimeout: resourceClusterParameterGroupDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -104,22 +104,20 @@ func resourceClusterParameterGroupCreate(ctx context.Context, d *schema.Resource tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) groupName := create.Name(d.Get("name").(string), d.Get("name_prefix").(string)) - - createOpts := rds.CreateDBClusterParameterGroupInput{ + input := &rds.CreateDBClusterParameterGroupInput{ DBClusterParameterGroupName: aws.String(groupName), DBParameterGroupFamily: aws.String(d.Get("family").(string)), Description: aws.String(d.Get("description").(string)), Tags: Tags(tags.IgnoreAWS()), } - log.Printf("[DEBUG] Create DB Cluster Parameter Group: %s", groupName) - output, err := conn.CreateDBClusterParameterGroupWithContext(ctx, &createOpts) + output, err := conn.CreateDBClusterParameterGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating DB Cluster Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "creating DB Cluster Parameter Group (%s): %s", groupName, err) } - d.SetId(aws.StringValue(createOpts.DBClusterParameterGroupName)) - log.Printf("[INFO] DB Cluster Parameter Group ID: %s", d.Id()) + d.SetId(groupName) // Set for update d.Set("arn", output.DBClusterParameterGroup.DBClusterParameterGroupArn) @@ -133,45 +131,46 @@ func resourceClusterParameterGroupRead(ctx context.Context, d *schema.ResourceDa defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - describeOpts := rds.DescribeDBClusterParameterGroupsInput{ - DBClusterParameterGroupName: aws.String(d.Id()), - } + dbClusterParameterGroup, err := FindDBClusterParameterGroupByName(ctx, conn, d.Id()) - describeResp, err := conn.DescribeDBClusterParameterGroupsWithContext(ctx, &describeOpts) - if err != nil { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "DBParameterGroupNotFound" { - log.Printf("[WARN] DB Cluster Parameter Group (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } - - return sdkdiag.AppendErrorf(diags, "reading RDS Cluster Parameter Group (%s): %s", d.Id(), err) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] RDS DB Cluster Parameter Group (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - if len(describeResp.DBClusterParameterGroups) != 1 || - aws.StringValue(describeResp.DBClusterParameterGroups[0].DBClusterParameterGroupName) != d.Id() { - return sdkdiag.AppendErrorf(diags, "Unable to find Cluster Parameter Group: %#v", describeResp.DBClusterParameterGroups) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading RDS DB Cluster Parameter Group (%s): %s", d.Id(), err) } - arn := aws.StringValue(describeResp.DBClusterParameterGroups[0].DBClusterParameterGroupArn) + arn := aws.StringValue(dbClusterParameterGroup.DBClusterParameterGroupArn) d.Set("arn", arn) - d.Set("description", describeResp.DBClusterParameterGroups[0].Description) - d.Set("family", describeResp.DBClusterParameterGroups[0].DBParameterGroupFamily) - d.Set("name", describeResp.DBClusterParameterGroups[0].DBClusterParameterGroupName) - d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(describeResp.DBClusterParameterGroups[0].DBClusterParameterGroupName))) + d.Set("description", dbClusterParameterGroup.Description) + d.Set("family", dbClusterParameterGroup.DBParameterGroupFamily) + d.Set("name", dbClusterParameterGroup.DBClusterParameterGroupName) + d.Set("name_prefix", create.NamePrefixFromName(aws.StringValue(dbClusterParameterGroup.DBClusterParameterGroupName))) // Only include user customized parameters as there's hundreds of system/default ones - describeParametersOpts := rds.DescribeDBClusterParametersInput{ + input := &rds.DescribeDBClusterParametersInput{ DBClusterParameterGroupName: aws.String(d.Id()), Source: aws.String("user"), } - var parameters []*rds.Parameter - err = conn.DescribeDBClusterParametersPagesWithContext(ctx, &describeParametersOpts, - func(describeParametersResp *rds.DescribeDBClusterParametersOutput, lastPage bool) bool { - parameters = append(parameters, describeParametersResp.Parameters...) + + err = conn.DescribeDBClusterParametersPagesWithContext(ctx, input, func(page *rds.DescribeDBClusterParametersOutput, lastPage bool) bool { + if page == nil { return !lastPage - }) + } + + for _, v := range page.Parameters { + if v != nil { + parameters = append(parameters, v) + } + } + + return !lastPage + }) + if err != nil { return sdkdiag.AppendErrorf(diags, "reading RDS Cluster Parameter Group (%s) parameters: %s", d.Id(), err) } @@ -180,22 +179,21 @@ func resourceClusterParameterGroupRead(ctx context.Context, d *schema.ResourceDa return sdkdiag.AppendErrorf(diags, "setting parameter: %s", err) } - resp, err := conn.ListTagsForResourceWithContext(ctx, &rds.ListTagsForResourceInput{ - ResourceName: aws.String(arn), - }) - if err != nil { - log.Printf("[WARN] Error retrieving tags for DB Cluster Parameter Group (%s). Ignoring: %s", d.Id(), err) - } + tags, err := ListTags(ctx, conn, arn) - tags := KeyValueTags(resp.TagList).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + if err != nil { + log.Printf("[WARN] listing tags for RDS DB Cluster Parameter Group (%s): %s", d.Id(), err) + } else { + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) - //lintignore:AWSR002 - if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { - return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) - } + //lintignore:AWSR002 + if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { + return sdkdiag.AppendErrorf(diags, "setting tags: %s", err) + } - if err := d.Set("tags_all", tags.Map()); err != nil { - return sdkdiag.AppendErrorf(diags, "setting tags_all: %s", err) + if err := d.Set("tags_all", tags.Map()); err != nil { + return sdkdiag.AppendErrorf(diags, "setting tags_all: %s", err) + } } return diags @@ -230,15 +228,15 @@ func resourceClusterParameterGroupUpdate(ctx context.Context, d *schema.Resource paramsToModify, parameters = parameters[:clusterParameterGroupMaxParamsBulkEdit], parameters[clusterParameterGroupMaxParamsBulkEdit:] } - modifyOpts := rds.ModifyDBClusterParameterGroupInput{ + input := &rds.ModifyDBClusterParameterGroupInput{ DBClusterParameterGroupName: aws.String(d.Id()), Parameters: paramsToModify, } - log.Printf("[DEBUG] Modify DB Cluster Parameter Group: %s", modifyOpts) - _, err := conn.ModifyDBClusterParameterGroupWithContext(ctx, &modifyOpts) + _, err := conn.ModifyDBClusterParameterGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying DB Cluster Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "modifying DB Cluster Parameter Group (%s): %s", d.Id(), err) } } } @@ -264,7 +262,6 @@ func resourceClusterParameterGroupUpdate(ctx context.Context, d *schema.Resource } if len(resetParameters) > 0 { for resetParameters != nil { - parameterGroupName := d.Get("name").(string) var paramsToReset []*rds.Parameter if len(resetParameters) <= clusterParameterGroupMaxParamsBulkEdit { paramsToReset, resetParameters = resetParameters[:], nil @@ -272,15 +269,14 @@ func resourceClusterParameterGroupUpdate(ctx context.Context, d *schema.Resource paramsToReset, resetParameters = resetParameters[:clusterParameterGroupMaxParamsBulkEdit], resetParameters[clusterParameterGroupMaxParamsBulkEdit:] } - resetOpts := rds.ResetDBClusterParameterGroupInput{ - DBClusterParameterGroupName: aws.String(parameterGroupName), + input := &rds.ResetDBClusterParameterGroupInput{ + DBClusterParameterGroupName: aws.String(d.Id()), Parameters: paramsToReset, ResetAllParameters: aws.Bool(false), } - log.Printf("[DEBUG] Reset DB Cluster Parameter Group: %s", resetOpts) - err := resource.RetryContext(ctx, 3*time.Minute, func() *resource.RetryError { - _, err := conn.ResetDBClusterParameterGroupWithContext(ctx, &resetOpts) + err := resource.Retry(3*time.Minute, func() *resource.RetryError { + _, err := conn.ResetDBClusterParameterGroupWithContext(ctx, input) if err != nil { if tfawserr.ErrMessageContains(err, "InvalidDBParameterGroupState", "has pending changes") { return resource.RetryableError(err) @@ -291,11 +287,11 @@ func resourceClusterParameterGroupUpdate(ctx context.Context, d *schema.Resource }) if tfresource.TimedOut(err) { - _, err = conn.ResetDBClusterParameterGroupWithContext(ctx, &resetOpts) + _, err = conn.ResetDBClusterParameterGroupWithContext(ctx, input) } if err != nil { - return sdkdiag.AppendErrorf(diags, "resetting DB Cluster Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "resetting DB Cluster Parameter Group (%s): %s", d.Id(), err) } } } @@ -312,15 +308,17 @@ func resourceClusterParameterGroupUpdate(ctx context.Context, d *schema.Resource return append(diags, resourceClusterParameterGroupRead(ctx, d, meta)...) } -func resourceClusterParameterGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { +func resourceClusterParameterGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics conn := meta.(*conns.AWSClient).RDSClient() - input := rds_sdkv2.DeleteDBClusterParameterGroupInput{ + + input := &rds_sdkv2.DeleteDBClusterParameterGroupInput{ DBClusterParameterGroupName: aws.String(d.Id()), } - log.Printf("[DEBUG] Deleting RDS Cluster Parameter Group: %s", d.Id()) + log.Printf("[DEBUG] Deleting RDS DB Cluster Parameter Group: %s", d.Id()) err := resource.RetryContext(ctx, 3*time.Minute, func() *resource.RetryError { - _, err := conn.DeleteDBClusterParameterGroup(ctx, &input) + _, err := conn.DeleteDBClusterParameterGroup(ctx, input) if errs.IsA[*types.DBParameterGroupNotFoundFault](err) { return nil } else if errs.IsA[*types.InvalidDBParameterGroupStateFault](err) { @@ -332,10 +330,46 @@ func resourceClusterParameterGroupDelete(ctx context.Context, d *schema.Resource return nil }) if tfresource.TimedOut(err) { - _, err = conn.DeleteDBClusterParameterGroup(ctx, &input) + _, err = conn.DeleteDBClusterParameterGroup(ctx, input) } + if err != nil { return sdkdiag.AppendErrorf(diags, "deleting RDS Cluster Parameter Group (%s): %s", d.Id(), err) } - return nil + + return diags +} + +func FindDBClusterParameterGroupByName(ctx context.Context, conn *rds.RDS, name string) (*rds.DBClusterParameterGroup, error) { + input := &rds.DescribeDBClusterParameterGroupsInput{ + DBClusterParameterGroupName: aws.String(name), + } + + output, err := conn.DescribeDBClusterParameterGroupsWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBParameterGroupNotFoundFault) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.DBClusterParameterGroups) == 0 || output.DBClusterParameterGroups[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + dbClusterParameterGroup := output.DBClusterParameterGroups[0] + + // Eventual consistency check. + if aws.StringValue(dbClusterParameterGroup.DBClusterParameterGroupName) != name { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return dbClusterParameterGroup, nil } diff --git a/internal/service/rds/cluster_parameter_group_test.go b/internal/service/rds/cluster_parameter_group_test.go index 37d0ff2d127..594a31a806c 100644 --- a/internal/service/rds/cluster_parameter_group_test.go +++ b/internal/service/rds/cluster_parameter_group_test.go @@ -5,23 +5,23 @@ import ( "errors" "fmt" "testing" - "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/rds" 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" + tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRDSClusterParameterGroup_basic(t *testing.T) { ctx := acctest.Context(t) var v rds.DBClusterParameterGroup resourceName := "aws_rds_cluster_parameter_group.test" - parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -30,12 +30,12 @@ func TestAccRDSClusterParameterGroup_basic(t *testing.T) { CheckDestroy: testAccCheckClusterParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccClusterParameterGroupConfig_basic(parameterGroupName), + Config: testAccClusterParameterGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccCheckClusterParameterGroupAttributes(&v, parameterGroupName), - acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "rds", fmt.Sprintf("cluster-pg:%s", parameterGroupName)), - resource.TestCheckResourceAttr(resourceName, "name", parameterGroupName), + testAccCheckClusterParameterGroupAttributes(&v, rName), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "rds", fmt.Sprintf("cluster-pg:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "aurora5.6"), resource.TestCheckResourceAttr(resourceName, "description", "Test cluster parameter group for terraform"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ @@ -50,7 +50,7 @@ func TestAccRDSClusterParameterGroup_basic(t *testing.T) { "name": "character_set_client", "value": "utf8", }), - resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -59,11 +59,11 @@ func TestAccRDSClusterParameterGroup_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccClusterParameterGroupConfig_addParameters(parameterGroupName), + Config: testAccClusterParameterGroupConfig_addParameters(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccCheckClusterParameterGroupAttributes(&v, parameterGroupName), - resource.TestCheckResourceAttr(resourceName, "name", parameterGroupName), + testAccCheckClusterParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "aurora5.6"), resource.TestCheckResourceAttr(resourceName, "description", "Test cluster parameter group for terraform"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ @@ -86,14 +86,14 @@ func TestAccRDSClusterParameterGroup_basic(t *testing.T) { "name": "character_set_client", "value": "utf8", }), - resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { - Config: testAccClusterParameterGroupConfig_basic(parameterGroupName), + Config: testAccClusterParameterGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccCheckClusterParameterGroupAttributes(&v, parameterGroupName), + testAccCheckClusterParameterGroupAttributes(&v, rName), testAccCheckClusterParameterNotUserDefined(ctx, resourceName, "collation_connection"), testAccCheckClusterParameterNotUserDefined(ctx, resourceName, "collation_server"), resource.TestCheckResourceAttr(resourceName, "parameter.#", "3"), @@ -109,6 +109,77 @@ func TestAccRDSClusterParameterGroup_basic(t *testing.T) { "name": "character_set_client", "value": "utf8", }), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, + }, + }) +} + +func TestAccRDSClusterParameterGroup_disappears(t *testing.T) { + ctx := acctest.Context(t) + var v rds.DBClusterParameterGroup + resourceName := "aws_rds_cluster_parameter_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterParameterGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterParameterGroupConfig_basic(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfrds.ResourceClusterParameterGroup(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccRDSClusterParameterGroup_tags(t *testing.T) { + ctx := acctest.Context(t) + var v rds.DBClusterParameterGroup + resourceName := "aws_rds_cluster_parameter_group.test" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(t) }, + ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckClusterParameterGroupDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccClusterParameterGroupConfig_tags1(rName, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccClusterParameterGroupConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccClusterParameterGroupConfig_tags1(rName, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, }, @@ -118,7 +189,7 @@ func TestAccRDSClusterParameterGroup_basic(t *testing.T) { func TestAccRDSClusterParameterGroup_withApplyMethod(t *testing.T) { ctx := acctest.Context(t) var v rds.DBClusterParameterGroup - parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_rds_cluster_parameter_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -128,12 +199,12 @@ func TestAccRDSClusterParameterGroup_withApplyMethod(t *testing.T) { CheckDestroy: testAccCheckClusterParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccClusterParameterGroupConfig_applyMethod(parameterGroupName), + Config: testAccClusterParameterGroupConfig_applyMethod(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccCheckClusterParameterGroupAttributes(&v, parameterGroupName), - acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "rds", fmt.Sprintf("cluster-pg:%s", parameterGroupName)), - resource.TestCheckResourceAttr(resourceName, "name", parameterGroupName), + testAccCheckClusterParameterGroupAttributes(&v, rName), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "rds", fmt.Sprintf("cluster-pg:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "aurora5.6"), resource.TestCheckResourceAttr(resourceName, "description", "Test cluster parameter group for terraform"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ @@ -269,35 +340,11 @@ func TestAccRDSClusterParameterGroup_GeneratedName_parameter(t *testing.T) { }) } -func TestAccRDSClusterParameterGroup_disappears(t *testing.T) { - ctx := acctest.Context(t) - var v rds.DBClusterParameterGroup - resourceName := "aws_rds_cluster_parameter_group.test" - parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-terraform-%d", sdkacctest.RandInt()) - - resource.ParallelTest(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(t) }, - ErrorCheck: acctest.ErrorCheck(t, rds.EndpointsID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckClusterParameterGroupDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccClusterParameterGroupConfig_basic(parameterGroupName), - Check: resource.ComposeTestCheckFunc( - testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccClusterParameterGroupDisappears(ctx, &v), - ), - ExpectNonEmptyPlan: true, - }, - }, - }) -} - func TestAccRDSClusterParameterGroup_only(t *testing.T) { ctx := acctest.Context(t) var v rds.DBClusterParameterGroup resourceName := "aws_rds_cluster_parameter_group.test" - parameterGroupName := fmt.Sprintf("cluster-parameter-group-test-tf-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -306,16 +353,13 @@ func TestAccRDSClusterParameterGroup_only(t *testing.T) { CheckDestroy: testAccCheckClusterParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccClusterParameterGroupConfig_only(parameterGroupName), + Config: testAccClusterParameterGroupConfig_only(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccCheckClusterParameterGroupAttributes(&v, parameterGroupName), - resource.TestCheckResourceAttr( - resourceName, "name", parameterGroupName), - resource.TestCheckResourceAttr( - resourceName, "family", "aurora5.6"), - resource.TestCheckResourceAttr( - resourceName, "description", "Managed by Terraform"), + testAccCheckClusterParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "family", "aurora5.6"), + resource.TestCheckResourceAttr(resourceName, "description", "Managed by Terraform"), ), }, { @@ -331,7 +375,7 @@ func TestAccRDSClusterParameterGroup_updateParameters(t *testing.T) { ctx := acctest.Context(t) var v rds.DBClusterParameterGroup resourceName := "aws_rds_cluster_parameter_group.test" - groupName := fmt.Sprintf("cluster-parameter-group-test-tf-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -340,11 +384,11 @@ func TestAccRDSClusterParameterGroup_updateParameters(t *testing.T) { CheckDestroy: testAccCheckClusterParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccClusterParameterGroupConfig_updateParametersInitial(groupName), + Config: testAccClusterParameterGroupConfig_updateParametersInitial(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccCheckClusterParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckClusterParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "aurora5.6"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ "name": "character_set_results", @@ -366,10 +410,10 @@ func TestAccRDSClusterParameterGroup_updateParameters(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccClusterParameterGroupConfig_updateParametersUpdated(groupName), + Config: testAccClusterParameterGroupConfig_updateParametersUpdated(rName), Check: resource.ComposeTestCheckFunc( testAccCheckClusterParameterGroupExists(ctx, resourceName, &v), - testAccCheckClusterParameterGroupAttributes(&v, groupName), + testAccCheckClusterParameterGroupAttributes(&v, rName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ "name": "character_set_results", "value": "ascii", @@ -434,26 +478,17 @@ func testAccCheckClusterParameterGroupDestroy(ctx context.Context) resource.Test continue } - // Try to find the Group - resp, err := conn.DescribeDBClusterParameterGroupsWithContext(ctx, &rds.DescribeDBClusterParameterGroupsInput{ - DBClusterParameterGroupName: aws.String(rs.Primary.ID), - }) + _, err := tfrds.FindDBClusterParameterGroupByName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.DBClusterParameterGroups) != 0 && - *resp.DBClusterParameterGroups[0].DBClusterParameterGroupName == rs.Primary.ID { - return errors.New("DB Cluster Parameter Group still exists") - } + if tfresource.NotFound(err) { + continue } - // Verify the error - newerr, ok := err.(awserr.Error) - if !ok { - return err - } - if newerr.Code() != "DBParameterGroupNotFound" { + if err != nil { return err } + + return fmt.Errorf("RDS DB Cluster Parameter Group %s still exists", rs.Primary.ID) } return nil @@ -507,34 +542,6 @@ func testAccCheckClusterParameterGroupAttributes(v *rds.DBClusterParameterGroup, } } -func testAccClusterParameterGroupDisappears(ctx context.Context, v *rds.DBClusterParameterGroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn() - opts := &rds.DeleteDBClusterParameterGroupInput{ - DBClusterParameterGroupName: v.DBClusterParameterGroupName, - } - if _, err := conn.DeleteDBClusterParameterGroupWithContext(ctx, opts); err != nil { - return err - } - return resource.RetryContext(ctx, 40*time.Minute, func() *resource.RetryError { - opts := &rds.DescribeDBClusterParameterGroupsInput{ - DBClusterParameterGroupName: v.DBClusterParameterGroupName, - } - _, err := conn.DescribeDBClusterParameterGroupsWithContext(ctx, opts) - if err != nil { - dbparamgrouperr, ok := err.(awserr.Error) - if ok && dbparamgrouperr.Code() == "DBParameterGroupNotFound" { - return nil - } - return resource.NonRetryableError( - fmt.Errorf("Error retrieving DB Cluster Parameter Groups: %s", err)) - } - return resource.RetryableError(fmt.Errorf( - "Waiting for cluster parameter group to be deleted: %v", v.DBClusterParameterGroupName)) - }) - } -} - func testAccCheckClusterParameterGroupExists(ctx context.Context, n string, v *rds.DBClusterParameterGroup) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -543,36 +550,27 @@ func testAccCheckClusterParameterGroupExists(ctx context.Context, n string, v *r } if rs.Primary.ID == "" { - return errors.New("No DB Cluster Parameter Group ID is set") + return errors.New("No RDS DB Cluster Parameter Group ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn() - opts := rds.DescribeDBClusterParameterGroupsInput{ - DBClusterParameterGroupName: aws.String(rs.Primary.ID), - } - - resp, err := conn.DescribeDBClusterParameterGroupsWithContext(ctx, &opts) + output, err := tfrds.FindDBClusterParameterGroupByName(ctx, conn, rs.Primary.ID) if err != nil { return err } - if len(resp.DBClusterParameterGroups) != 1 || - *resp.DBClusterParameterGroups[0].DBClusterParameterGroupName != rs.Primary.ID { - return errors.New("DB Cluster Parameter Group not found") - } - - *v = *resp.DBClusterParameterGroups[0] + *v = *output return nil } } -func testAccClusterParameterGroupConfig_basic(name string) string { +func testAccClusterParameterGroupConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster_parameter_group" "test" { - name = "%s" + name = %[1]q family = "aurora5.6" description = "Test cluster parameter group for terraform" @@ -590,18 +588,14 @@ resource "aws_rds_cluster_parameter_group" "test" { name = "character_set_results" value = "utf8" } - - tags = { - foo = "bar" - } } -`, name) +`, rName) } -func testAccClusterParameterGroupConfig_applyMethod(name string) string { +func testAccClusterParameterGroupConfig_applyMethod(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster_parameter_group" "test" { - name = "%s" + name = %[1]q family = "aurora5.6" description = "Test cluster parameter group for terraform" @@ -615,18 +609,14 @@ resource "aws_rds_cluster_parameter_group" "test" { value = "utf8" apply_method = "pending-reboot" } - - tags = { - foo = "bar" - } } -`, name) +`, rName) } -func testAccClusterParameterGroupConfig_addParameters(name string) string { +func testAccClusterParameterGroupConfig_addParameters(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster_parameter_group" "test" { - name = "%s" + name = %[1]q family = "aurora5.6" description = "Test cluster parameter group for terraform" @@ -654,28 +644,23 @@ resource "aws_rds_cluster_parameter_group" "test" { name = "collation_connection" value = "utf8_unicode_ci" } - - tags = { - foo = "bar" - baz = "foo" - } } -`, name) +`, rName) } -func testAccClusterParameterGroupConfig_only(name string) string { +func testAccClusterParameterGroupConfig_only(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster_parameter_group" "test" { - name = "%s" + name = %[1]q family = "aurora5.6" } -`, name) +`, rName) } -func testAccClusterParameterGroupConfig_updateParametersInitial(name string) string { +func testAccClusterParameterGroupConfig_updateParametersInitial(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster_parameter_group" "test" { - name = "%s" + name = %[1]q family = "aurora5.6" parameter { @@ -693,13 +678,13 @@ resource "aws_rds_cluster_parameter_group" "test" { value = "utf8" } } -`, name) +`, rName) } -func testAccClusterParameterGroupConfig_updateParametersUpdated(name string) string { +func testAccClusterParameterGroupConfig_updateParametersUpdated(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster_parameter_group" "test" { - name = "%s" + name = %[1]q family = "aurora5.6" parameter { @@ -717,13 +702,13 @@ resource "aws_rds_cluster_parameter_group" "test" { value = "ascii" } } -`, name) +`, rName) } func testAccClusterParameterGroupConfig_upperCase(rName string) string { return fmt.Sprintf(` resource "aws_rds_cluster_parameter_group" "test" { - name = "%s" + name = %[1]q family = "aurora5.6" parameter { @@ -774,3 +759,30 @@ resource "aws_rds_cluster_parameter_group" "test" { } } ` + +func testAccClusterParameterGroupConfig_tags1(rName, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_rds_cluster_parameter_group" "test" { + name = %[1]q + family = "aurora5.6" + + tags = { + %[2]q = %[3]q + } +} +`, rName, tagKey1, tagValue1) +} + +func testAccClusterParameterGroupConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_rds_cluster_parameter_group" "test" { + name = %[1]q + family = "aurora5.6" + + tags = { + %[2]q = %[3]q + %[4]q = %[5]q + } +} +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/rds/parameter_group.go b/internal/service/rds/parameter_group.go index c98bf35bb7f..a4073d92b6d 100644 --- a/internal/service/rds/parameter_group.go +++ b/internal/service/rds/parameter_group.go @@ -31,6 +31,7 @@ func ResourceParameterGroup() *schema.Resource { ReadWithoutTimeout: resourceParameterGroupRead, UpdateWithoutTimeout: resourceParameterGroupUpdate, DeleteWithoutTimeout: resourceParameterGroupDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -40,6 +41,17 @@ func ResourceParameterGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "Managed by Terraform", + }, + "family": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, "name": { Type: schema.TypeString, Optional: true, @@ -56,17 +68,6 @@ func ResourceParameterGroup() *schema.Resource { ConflictsWith: []string{"name"}, ValidateFunc: validParamGroupNamePrefix, }, - "family": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "description": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "Managed by Terraform", - }, "parameter": { Type: schema.TypeSet, Optional: true, @@ -89,7 +90,6 @@ func ResourceParameterGroup() *schema.Resource { }, Set: resourceParameterHash, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -104,32 +104,24 @@ func resourceParameterGroupCreate(ctx context.Context, d *schema.ResourceData, m defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - var groupName string - if v, ok := d.GetOk("name"); ok { - groupName = v.(string) - } else if v, ok := d.GetOk("name_prefix"); ok { - groupName = resource.PrefixedUniqueId(v.(string)) - } else { - groupName = resource.UniqueId() - } - d.Set("name", groupName) - - createOpts := rds.CreateDBParameterGroupInput{ - DBParameterGroupName: aws.String(groupName), + name := create.Name(d.Get("name").(string), d.Get("name_prefix").(string)) + input := &rds.CreateDBParameterGroupInput{ DBParameterGroupFamily: aws.String(d.Get("family").(string)), + DBParameterGroupName: aws.String(name), Description: aws.String(d.Get("description").(string)), Tags: Tags(tags.IgnoreAWS()), } - log.Printf("[DEBUG] Create DB Parameter Group: %#v", createOpts) - resp, err := conn.CreateDBParameterGroupWithContext(ctx, &createOpts) + output, err := conn.CreateDBParameterGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating DB Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "creatingDB Parameter Group (%s): %s", name, err) } - d.SetId(aws.StringValue(resp.DBParameterGroup.DBParameterGroupName)) - d.Set("arn", resp.DBParameterGroup.DBParameterGroupArn) - log.Printf("[INFO] DB Parameter Group ID: %s", d.Id()) + d.SetId(aws.StringValue(output.DBParameterGroup.DBParameterGroupName)) + + // Set for update + d.Set("arn", output.DBParameterGroup.DBParameterGroupArn) return append(diags, resourceParameterGroupUpdate(ctx, d, meta)...) } @@ -140,33 +132,29 @@ func resourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, met defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - describeOpts := rds.DescribeDBParameterGroupsInput{ - DBParameterGroupName: aws.String(d.Id()), + dbParameterGroup, err := FindDBParameterGroupByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] RDS DB Parameter Group (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - describeResp, err := conn.DescribeDBParameterGroupsWithContext(ctx, &describeOpts) if err != nil { - if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBParameterGroupNotFoundFault) { - log.Printf("[WARN] DB Parameter Group (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } return sdkdiag.AppendErrorf(diags, "reading RDS DB Parameter Group (%s): %s", d.Id(), err) } - if len(describeResp.DBParameterGroups) != 1 || - aws.StringValue(describeResp.DBParameterGroups[0].DBParameterGroupName) != d.Id() { - return sdkdiag.AppendErrorf(diags, "Unable to find Parameter Group: %#v", describeResp.DBParameterGroups) - } - - d.Set("name", describeResp.DBParameterGroups[0].DBParameterGroupName) - d.Set("family", describeResp.DBParameterGroups[0].DBParameterGroupFamily) - d.Set("description", describeResp.DBParameterGroups[0].Description) + arn := aws.StringValue(dbParameterGroup.DBParameterGroupArn) + d.Set("arn", arn) + d.Set("description", dbParameterGroup.Description) + d.Set("family", dbParameterGroup.DBParameterGroupFamily) + d.Set("name", dbParameterGroup.DBParameterGroupName) - configParams := d.Get("parameter").(*schema.Set) - describeParametersOpts := rds.DescribeDBParametersInput{ + input := &rds.DescribeDBParametersInput{ DBParameterGroupName: aws.String(d.Id()), } + + configParams := d.Get("parameter").(*schema.Set) if configParams.Len() < 1 { // if we don't have any params in the ResourceData already, two possibilities // first, we don't have a config available to us. Second, we do, but it has @@ -177,17 +165,17 @@ func resourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, met // an empty list anyways, so we just make some unnecessary requests. But in // the more common case (I assume) of an import, this will make fewer requests // and "do the right thing". - describeParametersOpts.Source = aws.String("user") + input.Source = aws.String("user") } var parameters []*rds.Parameter - err = conn.DescribeDBParametersPagesWithContext(ctx, &describeParametersOpts, - func(describeParametersResp *rds.DescribeDBParametersOutput, lastPage bool) bool { - parameters = append(parameters, describeParametersResp.Parameters...) - return !lastPage - }) + err = conn.DescribeDBParametersPagesWithContext(ctx, input, func(page *rds.DescribeDBParametersOutput, lastPage bool) bool { + parameters = append(parameters, page.Parameters...) + return !lastPage + }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "reading RDS DB Parameter Group (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading RDS DB Parameter Group (%s) parameters: %s", d.Id(), err) } var userParams []*rds.Parameter @@ -230,18 +218,14 @@ func resourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, met } } - err = d.Set("parameter", flattenParameters(userParams)) - if err != nil { - return sdkdiag.AppendErrorf(diags, "setting 'parameter' in state: %s", err) + if err := d.Set("parameter", flattenParameters(userParams)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting parameter: %s", err) } - arn := aws.StringValue(describeResp.DBParameterGroups[0].DBParameterGroupArn) - d.Set("arn", arn) - - tags, err := ListTags(ctx, conn, d.Get("arn").(string)) + tags, err := ListTags(ctx, conn, arn) if err != nil { - return sdkdiag.AppendErrorf(diags, "listing tags for RDS DB Parameter Group (%s): %s", d.Get("arn").(string), err) + return sdkdiag.AppendErrorf(diags, "listing tags for RDS DB Parameter Group (%s): %s", arn, err) } tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) @@ -258,9 +242,10 @@ func resourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, met return diags } -const maxParamModifyChunk = 20 - func resourceParameterGroupUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + const ( + maxParamModifyChunk = 20 + ) var diags diag.Diagnostics conn := meta.(*conns.AWSClient).RDSConn() @@ -287,15 +272,15 @@ func resourceParameterGroupUpdate(ctx context.Context, d *schema.ResourceData, m var paramsToModify []*rds.Parameter paramsToModify, parameters = ResourceParameterModifyChunk(parameters, maxParamModifyChunk) - modifyOpts := rds.ModifyDBParameterGroupInput{ - DBParameterGroupName: aws.String(d.Get("name").(string)), + input := &rds.ModifyDBParameterGroupInput{ + DBParameterGroupName: aws.String(d.Id()), Parameters: paramsToModify, } - log.Printf("[DEBUG] Modify DB Parameter Group: %s", modifyOpts) - _, err := conn.ModifyDBParameterGroupWithContext(ctx, &modifyOpts) + _, err := conn.ModifyDBParameterGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying DB Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "modifying DB Parameter Group (%s): %s", d.Id(), err) } } } @@ -328,17 +313,16 @@ func resourceParameterGroupUpdate(ctx context.Context, d *schema.ResourceData, m paramsToReset, resetParameters = resetParameters[:maxParamModifyChunk], resetParameters[maxParamModifyChunk:] } - parameterGroupName := d.Get("name").(string) - resetOpts := rds.ResetDBParameterGroupInput{ - DBParameterGroupName: aws.String(parameterGroupName), + input := &rds.ResetDBParameterGroupInput{ + DBParameterGroupName: aws.String(d.Id()), Parameters: paramsToReset, ResetAllParameters: aws.Bool(false), } - log.Printf("[DEBUG] Reset DB Parameter Group: %s", resetOpts) - _, err := conn.ResetDBParameterGroupWithContext(ctx, &resetOpts) + _, err := conn.ResetDBParameterGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "resetting DB Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "resetting DB Parameter Group (%s): %s", d.Id(), err) } } } @@ -357,13 +341,13 @@ func resourceParameterGroupUpdate(ctx context.Context, d *schema.ResourceData, m func resourceParameterGroupDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) (diags diag.Diagnostics) { conn := meta.(*conns.AWSClient).RDSClient() - deleteOpts := rds_sdkv2.DeleteDBParameterGroupInput{ + input := &rds_sdkv2.DeleteDBParameterGroupInput{ DBParameterGroupName: aws.String(d.Id()), } log.Printf("[DEBUG] Deleting RDS DB Parameter Group: %s", d.Id()) err := resource.RetryContext(ctx, 3*time.Minute, func() *resource.RetryError { - _, err := conn.DeleteDBParameterGroup(ctx, &deleteOpts) + _, err := conn.DeleteDBParameterGroup(ctx, input) if errs.IsA[*types.DBParameterGroupNotFoundFault](err) { return nil } else if errs.IsA[*types.InvalidDBParameterGroupStateFault](err) { @@ -375,7 +359,7 @@ func resourceParameterGroupDelete(ctx context.Context, d *schema.ResourceData, m return nil }) if tfresource.TimedOut(err) { - _, err = conn.DeleteDBParameterGroup(ctx, &deleteOpts) + _, err = conn.DeleteDBParameterGroup(ctx, input) } if err != nil { return sdkdiag.AppendErrorf(diags, "deleting RDS DB Parameter Group (%s): %s", d.Id(), err) @@ -383,6 +367,40 @@ func resourceParameterGroupDelete(ctx context.Context, d *schema.ResourceData, m return nil } +func FindDBParameterGroupByName(ctx context.Context, conn *rds.RDS, name string) (*rds.DBParameterGroup, error) { + input := &rds.DescribeDBParameterGroupsInput{ + DBParameterGroupName: aws.String(name), + } + + output, err := conn.DescribeDBParameterGroupsWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, rds.ErrCodeDBParameterGroupNotFoundFault) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.DBParameterGroups) == 0 || output.DBParameterGroups[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + dbParameterGroup := output.DBParameterGroups[0] + + // Eventual consistency check. + if aws.StringValue(dbParameterGroup.DBParameterGroupName) != name { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return dbParameterGroup, nil +} + func resourceParameterHash(v interface{}) int { var buf bytes.Buffer m := v.(map[string]interface{}) diff --git a/internal/service/rds/parameter_group_test.go b/internal/service/rds/parameter_group_test.go index acb3b9707d2..16ad1c28476 100644 --- a/internal/service/rds/parameter_group_test.go +++ b/internal/service/rds/parameter_group_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/rds" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -16,13 +15,14 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfrds "github.com/hashicorp/terraform-provider-aws/internal/service/rds" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRDSParameterGroup_basic(t *testing.T) { ctx := acctest.Context(t) var v rds.DBParameterGroup resourceName := "aws_db_parameter_group.test" - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -31,11 +31,11 @@ func TestAccRDSParameterGroup_basic(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_basic(groupName), + Config: testAccParameterGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ "name": "character_set_results", @@ -49,7 +49,7 @@ func TestAccRDSParameterGroup_basic(t *testing.T) { "name": "character_set_client", "value": "utf8", }), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(fmt.Sprintf("pg:%s$", groupName))), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(fmt.Sprintf("pg:%s$", rName))), ), }, { @@ -58,11 +58,11 @@ func TestAccRDSParameterGroup_basic(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccParameterGroupConfig_addParameters(groupName), + Config: testAccParameterGroupConfig_addParameters(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ "name": "collation_connection", @@ -84,14 +84,14 @@ func TestAccRDSParameterGroup_basic(t *testing.T) { "name": "character_set_client", "value": "utf8", }), - acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(fmt.Sprintf("pg:%s$", groupName))), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "rds", regexp.MustCompile(fmt.Sprintf("pg:%s$", rName))), ), }, { - Config: testAccParameterGroupConfig_basic(groupName), + Config: testAccParameterGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), + testAccCheckParameterGroupAttributes(&v, rName), testAccCheckParameterNotUserDefined(ctx, resourceName, "collation_connection"), testAccCheckParameterNotUserDefined(ctx, resourceName, "collation_server"), resource.TestCheckResourceAttr(resourceName, "parameter.#", "3"), @@ -115,7 +115,7 @@ func TestAccRDSParameterGroup_basic(t *testing.T) { func TestAccRDSParameterGroup_caseWithMixedParameters(t *testing.T) { ctx := acctest.Context(t) - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -124,7 +124,7 @@ func TestAccRDSParameterGroup_caseWithMixedParameters(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_caseWithMixedParameters(groupName), + Config: testAccParameterGroupConfig_caseWithMixedParameters(rName), Check: resource.ComposeTestCheckFunc(), }, }, @@ -135,7 +135,7 @@ func TestAccRDSParameterGroup_limit(t *testing.T) { ctx := acctest.Context(t) var v rds.DBParameterGroup resourceName := "aws_db_parameter_group.test" - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -144,11 +144,11 @@ func TestAccRDSParameterGroup_limit(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_exceedDefaultLimit(groupName), + Config: testAccParameterGroupConfig_exceedDefaultLimit(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"), resource.TestCheckResourceAttr(resourceName, "description", "RDS default parameter group: Exceed default AWS parameter group limit of twenty"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ @@ -323,11 +323,11 @@ func TestAccRDSParameterGroup_limit(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccParameterGroupConfig_updateExceedDefaultLimit(groupName), + Config: testAccParameterGroupConfig_updateExceedDefaultLimit(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"), resource.TestCheckResourceAttr(resourceName, "description", "Updated RDS default parameter group: Exceed default AWS parameter group limit of twenty"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ @@ -504,7 +504,7 @@ func TestAccRDSParameterGroup_disappears(t *testing.T) { ctx := acctest.Context(t) var v rds.DBParameterGroup resourceName := "aws_db_parameter_group.test" - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -513,10 +513,10 @@ func TestAccRDSParameterGroup_disappears(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_basic(groupName), + Config: testAccParameterGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParamaterGroupDisappears(ctx, &v), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfrds.ResourceParameterGroup(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -569,7 +569,7 @@ func TestAccRDSParameterGroup_withApplyMethod(t *testing.T) { ctx := acctest.Context(t) var v rds.DBParameterGroup resourceName := "aws_db_parameter_group.test" - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -578,11 +578,11 @@ func TestAccRDSParameterGroup_withApplyMethod(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_applyMethod(groupName), + Config: testAccParameterGroupConfig_applyMethod(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"), resource.TestCheckResourceAttr(resourceName, "description", "Managed by Terraform"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ @@ -610,7 +610,7 @@ func TestAccRDSParameterGroup_only(t *testing.T) { ctx := acctest.Context(t) var v rds.DBParameterGroup resourceName := "aws_db_parameter_group.test" - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -619,11 +619,11 @@ func TestAccRDSParameterGroup_only(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_only(groupName), + Config: testAccParameterGroupConfig_only(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"), ), }, @@ -640,7 +640,7 @@ func TestAccRDSParameterGroup_matchDefault(t *testing.T) { ctx := acctest.Context(t) var v rds.DBParameterGroup resourceName := "aws_db_parameter_group.test" - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -649,10 +649,10 @@ func TestAccRDSParameterGroup_matchDefault(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_includeDefault(groupName), + Config: testAccParameterGroupConfig_includeDefault(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "postgres9.4"), ), }, @@ -670,7 +670,7 @@ func TestAccRDSParameterGroup_updateParameters(t *testing.T) { ctx := acctest.Context(t) var v rds.DBParameterGroup resourceName := "aws_db_parameter_group.test" - groupName := fmt.Sprintf("parameter-group-test-terraform-%d", sdkacctest.RandInt()) + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -679,11 +679,11 @@ func TestAccRDSParameterGroup_updateParameters(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_updateParametersInitial(groupName), + Config: testAccParameterGroupConfig_updateParametersInitial(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), - resource.TestCheckResourceAttr(resourceName, "name", groupName), + testAccCheckParameterGroupAttributes(&v, rName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ "name": "character_set_results", @@ -705,10 +705,10 @@ func TestAccRDSParameterGroup_updateParameters(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccParameterGroupConfig_updateParametersUpdated(groupName), + Config: testAccParameterGroupConfig_updateParametersUpdated(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - testAccCheckParameterGroupAttributes(&v, groupName), + testAccCheckParameterGroupAttributes(&v, rName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ "name": "character_set_results", "value": "ascii", @@ -993,23 +993,6 @@ func TestDBParameterModifyChunk(t *testing.T) { } } -func testAccCheckParamaterGroupDisappears(ctx context.Context, v *rds.DBParameterGroup) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn() - - for _, rs := range s.RootModule().Resources { - if rs.Type != "aws_db_parameter_group" { - continue - } - _, err := conn.DeleteDBParameterGroupWithContext(ctx, &rds.DeleteDBParameterGroupInput{ - DBParameterGroupName: v.DBParameterGroupName, - }) - return err - } - return nil - } -} - func testAccCheckParameterGroupDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn() @@ -1019,26 +1002,17 @@ func testAccCheckParameterGroupDestroy(ctx context.Context) resource.TestCheckFu continue } - // Try to find the Group - resp, err := conn.DescribeDBParameterGroupsWithContext(ctx, &rds.DescribeDBParameterGroupsInput{ - DBParameterGroupName: aws.String(rs.Primary.ID), - }) + _, err := tfrds.FindDBParameterGroupByName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.DBParameterGroups) != 0 && - *resp.DBParameterGroups[0].DBParameterGroupName == rs.Primary.ID { - return fmt.Errorf("DB Parameter Group still exists") - } + if tfresource.NotFound(err) { + continue } - // Verify the error - newerr, ok := err.(awserr.Error) - if !ok { - return err - } - if newerr.Code() != "DBParameterGroupNotFound" { + if err != nil { return err } + + return fmt.Errorf("RDS DB Parameter Group %s still exists", rs.Primary.ID) } return nil @@ -1059,35 +1033,26 @@ func testAccCheckParameterGroupAttributes(v *rds.DBParameterGroup, name string) } } -func testAccCheckParameterGroupExists(ctx context.Context, rName string, v *rds.DBParameterGroup) resource.TestCheckFunc { +func testAccCheckParameterGroupExists(ctx context.Context, n string, v *rds.DBParameterGroup) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[rName] + rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not found: %s", rName) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("No DB Parameter Group ID is set") + return fmt.Errorf("No RDS DB Parameter Group ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).RDSConn() - opts := rds.DescribeDBParameterGroupsInput{ - DBParameterGroupName: aws.String(rs.Primary.ID), - } - - resp, err := conn.DescribeDBParameterGroupsWithContext(ctx, &opts) + output, err := tfrds.FindDBParameterGroupByName(ctx, conn, rs.Primary.ID) if err != nil { return err } - if len(resp.DBParameterGroups) != 1 || - *resp.DBParameterGroups[0].DBParameterGroupName != rs.Primary.ID { - return fmt.Errorf("DB Parameter Group not found") - } - - *v = *resp.DBParameterGroups[0] + *v = *output return nil } diff --git a/internal/service/redshift/flex.go b/internal/service/redshift/flex.go index 4fb913452ac..0ee0347f974 100644 --- a/internal/service/redshift/flex.go +++ b/internal/service/redshift/flex.go @@ -6,29 +6,6 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/flex" ) -func ExpandParameters(configured []interface{}) []*redshift.Parameter { - var parameters []*redshift.Parameter - - // Loop over our configured parameters and create - // an array of aws-sdk-go compatible objects - for _, pRaw := range configured { - data := pRaw.(map[string]interface{}) - - if data["name"].(string) == "" { - continue - } - - p := &redshift.Parameter{ - ParameterName: aws.String(data["name"].(string)), - ParameterValue: aws.String(data["value"].(string)), - } - - parameters = append(parameters, p) - } - - return parameters -} - func flattenLogging(ls *redshift.LoggingStatus) []interface{} { if ls == nil { return []interface{}{} @@ -51,18 +28,6 @@ func flattenLogging(ls *redshift.LoggingStatus) []interface{} { return []interface{}{cfg} } -// Flattens an array of Redshift Parameters into a []map[string]interface{} -func FlattenParameters(list []*redshift.Parameter) []map[string]interface{} { - result := make([]map[string]interface{}, 0, len(list)) - for _, i := range list { - result = append(result, map[string]interface{}{ - "name": aws.StringValue(i.ParameterName), - "value": aws.StringValue(i.ParameterValue), - }) - } - return result -} - func flattenSnapshotCopy(scs *redshift.ClusterSnapshotCopyStatus) []interface{} { if scs == nil { return []interface{}{} diff --git a/internal/service/redshift/flex_test.go b/internal/service/redshift/flex_test.go deleted file mode 100644 index fc35a664a69..00000000000 --- a/internal/service/redshift/flex_test.go +++ /dev/null @@ -1,64 +0,0 @@ -package redshift - -import ( - "reflect" - "testing" - - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/redshift" -) - -func TestExpandParameters(t *testing.T) { - t.Parallel() - - expanded := []interface{}{ - map[string]interface{}{ - "name": "character_set_client", - "value": "utf8", - }, - } - parameters := ExpandParameters(expanded) - - expected := &redshift.Parameter{ - ParameterName: aws.String("character_set_client"), - ParameterValue: aws.String("utf8"), - } - - if !reflect.DeepEqual(parameters[0], expected) { - t.Fatalf( - "Got:\n\n%#v\n\nExpected:\n\n%#v\n", - parameters[0], - expected) - } -} - -func TestFlattenParameters(t *testing.T) { - t.Parallel() - - cases := []struct { - Input []*redshift.Parameter - Output []map[string]interface{} - }{ - { - Input: []*redshift.Parameter{ - { - ParameterName: aws.String("character_set_client"), - ParameterValue: aws.String("utf8"), - }, - }, - Output: []map[string]interface{}{ - { - "name": "character_set_client", - "value": "utf8", - }, - }, - }, - } - - for _, tc := range cases { - output := FlattenParameters(tc.Input) - if !reflect.DeepEqual(output, tc.Output) { - t.Fatalf("Got:\n\n%#v\n\nExpected:\n\n%#v", output, tc.Output) - } - } -} diff --git a/internal/service/redshift/parameter_group.go b/internal/service/redshift/parameter_group.go index 188522e99a6..1f815b1dabb 100644 --- a/internal/service/redshift/parameter_group.go +++ b/internal/service/redshift/parameter_group.go @@ -13,12 +13,14 @@ import ( "github.com/aws/aws-sdk-go/service/redshift" "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/create" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" 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" ) @@ -28,6 +30,7 @@ func ResourceParameterGroup() *schema.Resource { ReadWithoutTimeout: resourceParameterGroupRead, UpdateWithoutTimeout: resourceParameterGroupUpdate, DeleteWithoutTimeout: resourceParameterGroupDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -37,7 +40,17 @@ func ResourceParameterGroup() *schema.Resource { Type: schema.TypeString, Computed: true, }, - + "description": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Default: "Managed by Terraform", + }, + "family": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, "name": { Type: schema.TypeString, ForceNew: true, @@ -50,20 +63,6 @@ func ResourceParameterGroup() *schema.Resource { validation.StringDoesNotMatch(regexp.MustCompile(`-$`), "cannot end with a hyphen"), ), }, - - "family": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "description": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Default: "Managed by Terraform", - }, - "parameter": { Type: schema.TypeSet, Optional: true, @@ -81,7 +80,6 @@ func ResourceParameterGroup() *schema.Resource { }, Set: resourceParameterHash, }, - "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), }, @@ -96,31 +94,32 @@ func resourceParameterGroupCreate(ctx context.Context, d *schema.ResourceData, m defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - createOpts := redshift.CreateClusterParameterGroupInput{ - ParameterGroupName: aws.String(d.Get("name").(string)), - ParameterGroupFamily: aws.String(d.Get("family").(string)), + name := d.Get("name").(string) + input := &redshift.CreateClusterParameterGroupInput{ Description: aws.String(d.Get("description").(string)), + ParameterGroupFamily: aws.String(d.Get("family").(string)), + ParameterGroupName: aws.String(name), Tags: Tags(tags.IgnoreAWS()), } - log.Printf("[DEBUG] Create Redshift Parameter Group: %#v", createOpts) - _, err := conn.CreateClusterParameterGroupWithContext(ctx, &createOpts) + _, err := conn.CreateClusterParameterGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Redshift Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "creating Redshift Parameter Group (%s): %s", name, err) } - d.SetId(aws.StringValue(createOpts.ParameterGroupName)) + d.SetId(name) if v := d.Get("parameter").(*schema.Set); v.Len() > 0 { - parameters := ExpandParameters(v.List()) - - modifyOpts := redshift.ModifyClusterParameterGroupInput{ + input := &redshift.ModifyClusterParameterGroupInput{ ParameterGroupName: aws.String(d.Id()), - Parameters: parameters, + Parameters: expandParameters(v.List()), } - if _, err := conn.ModifyClusterParameterGroupWithContext(ctx, &modifyOpts); err != nil { - return sdkdiag.AppendErrorf(diags, "adding Redshift Parameter Group (%s) parameters: %s", d.Id(), err) + _, err := conn.ModifyClusterParameterGroupWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting Redshift Parameter Group (%s) parameters: %s", d.Id(), err) } } @@ -133,21 +132,18 @@ func resourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, met defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - describeOpts := redshift.DescribeClusterParameterGroupsInput{ - ParameterGroupName: aws.String(d.Id()), + parameterGroup, err := FindParameterGroupByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Redshift Parameter Group (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - describeResp, err := conn.DescribeClusterParameterGroupsWithContext(ctx, &describeOpts) if err != nil { return sdkdiag.AppendErrorf(diags, "reading Redshift Parameter Group (%s): %s", d.Id(), err) } - if len(describeResp.ParameterGroups) != 1 || - aws.StringValue(describeResp.ParameterGroups[0].ParameterGroupName) != d.Id() { - d.SetId("") - return sdkdiag.AppendErrorf(diags, "Unable to find Parameter Group: %#v", describeResp.ParameterGroups) - } - arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, Service: "redshift", @@ -155,13 +151,12 @@ func resourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, met AccountID: meta.(*conns.AWSClient).AccountID, Resource: fmt.Sprintf("parametergroup:%s", d.Id()), }.String() - d.Set("arn", arn) + d.Set("description", parameterGroup.Description) + d.Set("family", parameterGroup.ParameterGroupFamily) + d.Set("name", parameterGroup.ParameterGroupName) - d.Set("name", describeResp.ParameterGroups[0].ParameterGroupName) - d.Set("family", describeResp.ParameterGroups[0].ParameterGroupFamily) - d.Set("description", describeResp.ParameterGroups[0].Description) - tags := KeyValueTags(describeResp.ParameterGroups[0].Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + tags := KeyValueTags(parameterGroup.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -172,17 +167,19 @@ func resourceParameterGroupRead(ctx context.Context, d *schema.ResourceData, met return sdkdiag.AppendErrorf(diags, "setting tags_all: %s", err) } - describeParametersOpts := redshift.DescribeClusterParametersInput{ + input := &redshift.DescribeClusterParametersInput{ ParameterGroupName: aws.String(d.Id()), Source: aws.String("user"), } - describeParametersResp, err := conn.DescribeClusterParametersWithContext(ctx, &describeParametersOpts) + output, err := conn.DescribeClusterParametersWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "reading Redshift Parameter Group (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading Redshift Parameter Group (%s) parameters: %s", d.Id(), err) } - d.Set("parameter", FlattenParameters(describeParametersResp.Parameters)) + d.Set("parameter", flattenParameters(output.Parameters)) + return diags } @@ -198,23 +195,20 @@ func resourceParameterGroupUpdate(ctx context.Context, d *schema.ResourceData, m if n == nil { n = new(schema.Set) } - os := o.(*schema.Set) ns := n.(*schema.Set) - // Expand the "parameter" set to aws-sdk-go compat []redshift.Parameter - parameters := ExpandParameters(ns.Difference(os).List()) - + parameters := expandParameters(ns.Difference(os).List()) if len(parameters) > 0 { - modifyOpts := redshift.ModifyClusterParameterGroupInput{ - ParameterGroupName: aws.String(d.Get("name").(string)), + input := &redshift.ModifyClusterParameterGroupInput{ + ParameterGroupName: aws.String(d.Id()), Parameters: parameters, } - log.Printf("[DEBUG] Modify Redshift Parameter Group: %s", modifyOpts) - _, err := conn.ModifyClusterParameterGroupWithContext(ctx, &modifyOpts) + _, err := conn.ModifyClusterParameterGroupWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "modifying Redshift Parameter Group: %s", err) + return sdkdiag.AppendErrorf(diags, "setting Redshift Parameter Group (%s) parameters: %s", d.Id(), err) } } } @@ -223,7 +217,7 @@ func resourceParameterGroupUpdate(ctx context.Context, d *schema.ResourceData, m o, n := d.GetChange("tags_all") if err := UpdateTags(ctx, conn, d.Get("arn").(string), o, n); err != nil { - return sdkdiag.AppendErrorf(diags, "updating Redshift Parameter Group (%s) tags: %s", d.Get("arn").(string), err) + return sdkdiag.AppendErrorf(diags, "updating Redshift Parameter Group (%s) tags: %s", d.Id(), err) } } @@ -234,13 +228,54 @@ func resourceParameterGroupDelete(ctx context.Context, d *schema.ResourceData, m var diags diag.Diagnostics conn := meta.(*conns.AWSClient).RedshiftConn() + log.Printf("[DEBUG] Deleting Redshift Parameter Group: %s", d.Id()) _, err := conn.DeleteClusterParameterGroupWithContext(ctx, &redshift.DeleteClusterParameterGroupInput{ ParameterGroupName: aws.String(d.Id()), }) - if err != nil && tfawserr.ErrCodeEquals(err, "RedshiftParameterGroupNotFoundFault") { + + if tfawserr.ErrCodeEquals(err, redshift.ErrCodeClusterParameterGroupNotFoundFault) { return diags } - return sdkdiag.AppendErrorf(diags, "deleting Redshift Parameter Group (%s): %s", d.Id(), err) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting Redshift Parameter Group (%s): %s", d.Id(), err) + } + + return diags +} + +func FindParameterGroupByName(ctx context.Context, conn *redshift.Redshift, name string) (*redshift.ClusterParameterGroup, error) { + input := &redshift.DescribeClusterParameterGroupsInput{ + ParameterGroupName: aws.String(name), + } + + output, err := conn.DescribeClusterParameterGroupsWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, redshift.ErrCodeClusterParameterGroupNotFoundFault) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || len(output.ParameterGroups) == 0 || output.ParameterGroups[0] == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + parameterGroup := output.ParameterGroups[0] + + // Eventual consistency check. + if aws.StringValue(parameterGroup.ParameterGroupName) != name { + return nil, &resource.NotFoundError{ + LastRequest: input, + } + } + + return parameterGroup, nil } func resourceParameterHash(v interface{}) int { @@ -252,3 +287,37 @@ func resourceParameterHash(v interface{}) int { return create.StringHashcode(buf.String()) } + +func expandParameters(configured []interface{}) []*redshift.Parameter { + var parameters []*redshift.Parameter + + // Loop over our configured parameters and create + // an array of aws-sdk-go compatible objects + for _, pRaw := range configured { + data := pRaw.(map[string]interface{}) + + if data["name"].(string) == "" { + continue + } + + p := &redshift.Parameter{ + ParameterName: aws.String(data["name"].(string)), + ParameterValue: aws.String(data["value"].(string)), + } + + parameters = append(parameters, p) + } + + return parameters +} + +func flattenParameters(list []*redshift.Parameter) []map[string]interface{} { + result := make([]map[string]interface{}, 0, len(list)) + for _, i := range list { + result = append(result, map[string]interface{}{ + "name": aws.StringValue(i.ParameterName), + "value": aws.StringValue(i.ParameterValue), + }) + } + return result +} diff --git a/internal/service/redshift/parameter_group_test.go b/internal/service/redshift/parameter_group_test.go index 1248f18a3b3..28bffc19563 100644 --- a/internal/service/redshift/parameter_group_test.go +++ b/internal/service/redshift/parameter_group_test.go @@ -5,21 +5,21 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/redshift" 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" + tfredshift "github.com/hashicorp/terraform-provider-aws/internal/service/redshift" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccRedshiftParameterGroup_basic(t *testing.T) { ctx := acctest.Context(t) var v redshift.ClusterParameterGroup + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_redshift_parameter_group.test" - rInt := sdkacctest.RandInt() resource.ParallelTest(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(t) }, @@ -28,9 +28,27 @@ func TestAccRedshiftParameterGroup_basic(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_basic(rInt), - Check: resource.ComposeTestCheckFunc( + Config: testAccParameterGroupConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "redshift", fmt.Sprintf("parametergroup:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "description", "Managed by Terraform"), + resource.TestCheckResourceAttr(resourceName, "family", "redshift-1.0"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "parameter.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ + "name": "require_ssl", + "value": "true", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ + "name": "query_group", + "value": "example", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ + "name": "enable_user_activity_logging", + "value": "true", + }), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -42,10 +60,10 @@ func TestAccRedshiftParameterGroup_basic(t *testing.T) { }) } -func TestAccRedshiftParameterGroup_withParameters(t *testing.T) { +func TestAccRedshiftParameterGroup_disappears(t *testing.T) { ctx := acctest.Context(t) var v redshift.ClusterParameterGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_redshift_parameter_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -55,42 +73,21 @@ func TestAccRedshiftParameterGroup_withParameters(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_basic(rInt), + Config: testAccParameterGroupConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr( - resourceName, "name", fmt.Sprintf("test-terraform-%d", rInt)), - resource.TestCheckResourceAttr( - resourceName, "family", "redshift-1.0"), - resource.TestCheckResourceAttr( - resourceName, "description", "Managed by Terraform"), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ - "name": "require_ssl", - "value": "true", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ - "name": "query_group", - "value": "example", - }), - resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ - "name": "enable_user_activity_logging", - "value": "true", - }), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfredshift.ResourceParameterGroup(), resourceName), ), - }, - { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ExpectNonEmptyPlan: true, }, }, }) } -func TestAccRedshiftParameterGroup_withoutParameters(t *testing.T) { +func TestAccRedshiftParameterGroup_update(t *testing.T) { ctx := acctest.Context(t) var v redshift.ClusterParameterGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_redshift_parameter_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -100,15 +97,15 @@ func TestAccRedshiftParameterGroup_withoutParameters(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_only(rInt), - Check: resource.ComposeTestCheckFunc( + Config: testAccParameterGroupConfig_noParameters(rName), + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr( - resourceName, "name", fmt.Sprintf("test-terraform-%d", rInt)), - resource.TestCheckResourceAttr( - resourceName, "family", "redshift-1.0"), - resource.TestCheckResourceAttr( - resourceName, "description", "Test parameter group for terraform"), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "redshift", fmt.Sprintf("parametergroup:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "description", "Managed by Terraform"), + resource.TestCheckResourceAttr(resourceName, "family", "redshift-1.0"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "parameter.#", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), ), }, { @@ -116,14 +113,38 @@ func TestAccRedshiftParameterGroup_withoutParameters(t *testing.T) { ImportState: true, ImportStateVerify: true, }, + { + Config: testAccParameterGroupConfig_basic(rName), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckParameterGroupExists(ctx, resourceName, &v), + acctest.CheckResourceAttrRegionalARN(resourceName, "arn", "redshift", fmt.Sprintf("parametergroup:%s", rName)), + resource.TestCheckResourceAttr(resourceName, "description", "Managed by Terraform"), + resource.TestCheckResourceAttr(resourceName, "family", "redshift-1.0"), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "parameter.#", "3"), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ + "name": "require_ssl", + "value": "true", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ + "name": "query_group", + "value": "example", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{ + "name": "enable_user_activity_logging", + "value": "true", + }), + resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + ), + }, }, }) } -func TestAccRedshiftParameterGroup_withTags(t *testing.T) { +func TestAccRedshiftParameterGroup_tags(t *testing.T) { ctx := acctest.Context(t) var v redshift.ClusterParameterGroup - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_redshift_parameter_group.test" resource.ParallelTest(t, resource.TestCase{ @@ -133,14 +154,12 @@ func TestAccRedshiftParameterGroup_withTags(t *testing.T) { CheckDestroy: testAccCheckParameterGroupDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccParameterGroupConfig_tags(rInt, "aaa"), + Config: testAccParameterGroupConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr( - resourceName, "tags.%", "3"), - resource.TestCheckResourceAttr(resourceName, "tags.name", fmt.Sprintf("test-terraform-%d", rInt)), - resource.TestCheckResourceAttr(resourceName, "tags.environment", "Production"), - resource.TestCheckResourceAttr(resourceName, "tags.description", fmt.Sprintf("Test parameter group for terraform %s", "aaa")), + resource.TestCheckResourceAttr(resourceName, "description", "Test parameter group for terraform"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { @@ -149,21 +168,20 @@ func TestAccRedshiftParameterGroup_withTags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccParameterGroupConfig_tags(rInt, "bbb"), + Config: testAccParameterGroupConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr( - resourceName, "tags.%", "3"), - resource.TestCheckResourceAttr(resourceName, "tags.description", fmt.Sprintf("Test parameter group for terraform %s", "bbb")), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, { - Config: testAccParameterGroupConfig_tagsUpdate(rInt), + Config: testAccParameterGroupConfig_tags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckParameterGroupExists(ctx, resourceName, &v), - resource.TestCheckResourceAttr( - resourceName, "tags.%", "1"), - resource.TestCheckResourceAttr(resourceName, "tags.name", fmt.Sprintf("test-terraform-%d", rInt)), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, }, @@ -179,26 +197,17 @@ func testAccCheckParameterGroupDestroy(ctx context.Context) resource.TestCheckFu continue } - // Try to find the Group - resp, err := conn.DescribeClusterParameterGroupsWithContext(ctx, &redshift.DescribeClusterParameterGroupsInput{ - ParameterGroupName: aws.String(rs.Primary.ID), - }) + _, err := tfredshift.FindParameterGroupByName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.ParameterGroups) != 0 && - *resp.ParameterGroups[0].ParameterGroupName == rs.Primary.ID { - return fmt.Errorf("Redshift Parameter Group still exists") - } + if tfresource.NotFound(err) { + continue } - // Verify the error - newerr, ok := err.(awserr.Error) - if !ok { - return err - } - if newerr.Code() != "ClusterParameterGroupNotFound" { + if err != nil { return err } + + return fmt.Errorf("Redshift Parameter Group %s still exists", rs.Primary.ID) } return nil @@ -218,41 +227,22 @@ func testAccCheckParameterGroupExists(ctx context.Context, n string, v *redshift conn := acctest.Provider.Meta().(*conns.AWSClient).RedshiftConn() - opts := redshift.DescribeClusterParameterGroupsInput{ - ParameterGroupName: aws.String(rs.Primary.ID), - } - - resp, err := conn.DescribeClusterParameterGroupsWithContext(ctx, &opts) + output, err := tfredshift.FindParameterGroupByName(ctx, conn, rs.Primary.ID) if err != nil { return err } - if len(resp.ParameterGroups) != 1 || - *resp.ParameterGroups[0].ParameterGroupName != rs.Primary.ID { - return fmt.Errorf("Redshift Parameter Group not found") - } - - *v = *resp.ParameterGroups[0] + *v = *output return nil } } -func testAccParameterGroupConfig_only(rInt int) string { +func testAccParameterGroupConfig_basic(rName string) string { return fmt.Sprintf(` resource "aws_redshift_parameter_group" "test" { - name = "test-terraform-%d" - family = "redshift-1.0" - description = "Test parameter group for terraform" -} -`, rInt) -} - -func testAccParameterGroupConfig_basic(rInt int) string { - return fmt.Sprintf(` -resource "aws_redshift_parameter_group" "test" { - name = "test-terraform-%d" + name = %[1]q family = "redshift-1.0" parameter { @@ -270,35 +260,43 @@ resource "aws_redshift_parameter_group" "test" { value = "true" } } -`, rInt) +`, rName) +} + +func testAccParameterGroupConfig_noParameters(rName string) string { + return fmt.Sprintf(` +resource "aws_redshift_parameter_group" "test" { + name = %[1]q + family = "redshift-1.0" +} +`, rName) } -func testAccParameterGroupConfig_tags(rInt int, rString string) string { +func testAccParameterGroupConfig_tags1(rName, tagKey1, tagValue1 string) string { return fmt.Sprintf(` resource "aws_redshift_parameter_group" "test" { - name = "test-terraform-%[1]d" + name = %[1]q family = "redshift-1.0" description = "Test parameter group for terraform" tags = { - environment = "Production" - name = "test-terraform-%[1]d" - description = "Test parameter group for terraform %[2]s" + %[2]q = %[3]q } } -`, rInt, rString) +`, rName, tagKey1, tagValue1) } -func testAccParameterGroupConfig_tagsUpdate(rInt int) string { +func testAccParameterGroupConfig_tags2(rName, tagKey1, tagValue1, tagKey2, tagValue2 string) string { return fmt.Sprintf(` resource "aws_redshift_parameter_group" "test" { - name = "test-terraform-%[1]d" + name = %[1]q family = "redshift-1.0" description = "Test parameter group for terraform" tags = { - name = "test-terraform-%[1]d" + %[2]q = %[3]q + %[4]q = %[5]q } } -`, rInt) +`, rName, tagKey1, tagValue1, tagKey2, tagValue2) } diff --git a/internal/service/redshift/security_group.go b/internal/service/redshift/security_group.go index 72a5b7fbb6f..b6d7b2d864d 100644 --- a/internal/service/redshift/security_group.go +++ b/internal/service/redshift/security_group.go @@ -8,8 +8,8 @@ import ( "regexp" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/redshift" + "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" @@ -170,11 +170,11 @@ func resourceSecurityGroupDelete(ctx context.Context, d *schema.ResourceData, me _, err := conn.DeleteClusterSecurityGroupWithContext(ctx, &opts) + if tfawserr.ErrCodeEquals(err, "InvalidRedshiftSecurityGroup.NotFound") { + return diags + } + if err != nil { - newerr, ok := err.(awserr.Error) - if ok && newerr.Code() == "InvalidRedshiftSecurityGroup.NotFound" { - return diags - } return sdkdiag.AppendErrorf(diags, "deleting Redshift Security Group (%s): %s", d.Id(), err) } diff --git a/internal/service/sagemaker/model_test.go b/internal/service/sagemaker/model_test.go index c0e8630c0ae..569cccc1ea2 100644 --- a/internal/service/sagemaker/model_test.go +++ b/internal/service/sagemaker/model_test.go @@ -6,8 +6,8 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/sagemaker" + "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" @@ -419,11 +419,7 @@ func testAccCheckModelDestroy(ctx context.Context) resource.TestCheckFunc { return nil } - sagemakerErr, ok := err.(awserr.Error) - if !ok { - return err - } - if sagemakerErr.Code() != "ResourceNotFound" { + if !tfawserr.ErrCodeEquals(err, sagemaker.ErrCodeResourceNotFound) { return err } } diff --git a/internal/service/servicecatalog/portfolio.go b/internal/service/servicecatalog/portfolio.go index b95643d668e..3ed35f49c5d 100644 --- a/internal/service/servicecatalog/portfolio.go +++ b/internal/service/servicecatalog/portfolio.go @@ -6,8 +6,8 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/servicecatalog" + "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" @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" 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" ) @@ -45,17 +46,17 @@ func ResourcePortfolio() *schema.Resource { Type: schema.TypeString, Computed: true, }, - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringLenBetween(1, 100), - }, "description": { Type: schema.TypeString, Optional: true, Computed: true, ValidateFunc: validation.StringLenBetween(0, 2000), }, + "name": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 100), + }, "provider_name": { Type: schema.TypeString, Required: true, @@ -73,9 +74,11 @@ func resourcePortfolioCreate(ctx context.Context, d *schema.ResourceData, meta i conn := meta.(*conns.AWSClient).ServiceCatalogConn() defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) - input := servicecatalog.CreatePortfolioInput{ + + name := d.Get("name").(string) + input := &servicecatalog.CreatePortfolioInput{ AcceptLanguage: aws.String(AcceptLanguageEnglish), - DisplayName: aws.String(d.Get("name").(string)), + DisplayName: aws.String(name), IdempotencyToken: aws.String(resource.UniqueId()), Tags: Tags(tags.IgnoreAWS()), } @@ -88,12 +91,13 @@ func resourcePortfolioCreate(ctx context.Context, d *schema.ResourceData, meta i input.ProviderName = aws.String(v.(string)) } - log.Printf("[DEBUG] Creating Service Catalog Portfolio: %#v", input) - resp, err := conn.CreatePortfolioWithContext(ctx, &input) + output, err := conn.CreatePortfolioWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Creating Service Catalog Portfolio failed: %s", err.Error()) + return sdkdiag.AppendErrorf(diags, "creating Service Catalog Portfolio (%s): %s", name, err) } - d.SetId(aws.StringValue(resp.PortfolioDetail.Id)) + + d.SetId(aws.StringValue(output.PortfolioDetail.Id)) return append(diags, resourcePortfolioRead(ctx, d, meta)...) } @@ -104,31 +108,26 @@ func resourcePortfolioRead(ctx context.Context, d *schema.ResourceData, meta int defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - input := servicecatalog.DescribePortfolioInput{ - AcceptLanguage: aws.String(AcceptLanguageEnglish), + output, err := FindPortfolioByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] Service Catalog Portfolio (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - input.Id = aws.String(d.Id()) - log.Printf("[DEBUG] Reading Service Catalog Portfolio: %#v", input) - resp, err := conn.DescribePortfolioWithContext(ctx, &input) if err != nil { - if scErr, ok := err.(awserr.Error); ok && scErr.Code() == "ResourceNotFoundException" { - log.Printf("[WARN] Service Catalog Portfolio %q not found, removing from state", d.Id()) - d.SetId("") - return diags - } - return sdkdiag.AppendErrorf(diags, "Reading ServiceCatalog Portfolio '%s' failed: %s", *input.Id, err.Error()) - } - portfolioDetail := resp.PortfolioDetail - if err := d.Set("created_time", portfolioDetail.CreatedTime.Format(time.RFC3339)); err != nil { - log.Printf("[DEBUG] Error setting created_time: %s", err) + return sdkdiag.AppendErrorf(diags, "reading Service Catalog Portfolio (%s): %s", d.Id(), err) } + + portfolioDetail := output.PortfolioDetail d.Set("arn", portfolioDetail.ARN) + d.Set("created_time", portfolioDetail.CreatedTime.Format(time.RFC3339)) d.Set("description", portfolioDetail.Description) d.Set("name", portfolioDetail.DisplayName) d.Set("provider_name", portfolioDetail.ProviderName) - tags := KeyValueTags(resp.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + tags := KeyValueTags(output.Tags).IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -145,29 +144,26 @@ func resourcePortfolioRead(ctx context.Context, d *schema.ResourceData, meta int func resourcePortfolioUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ServiceCatalogConn() - input := servicecatalog.UpdatePortfolioInput{ + + input := &servicecatalog.UpdatePortfolioInput{ AcceptLanguage: aws.String(AcceptLanguageEnglish), Id: aws.String(d.Id()), } - if d.HasChange("name") { - v, _ := d.GetOk("name") - input.DisplayName = aws.String(v.(string)) - } - if d.HasChange("accept_language") { - v, _ := d.GetOk("accept_language") - input.AcceptLanguage = aws.String(v.(string)) + input.AcceptLanguage = aws.String(d.Get("accept_language").(string)) } if d.HasChange("description") { - v, _ := d.GetOk("description") - input.Description = aws.String(v.(string)) + input.Description = aws.String(d.Get("description").(string)) + } + + if d.HasChange("name") { + input.DisplayName = aws.String(d.Get("name").(string)) } if d.HasChange("provider_name") { - v, _ := d.GetOk("provider_name") - input.ProviderName = aws.String(v.(string)) + input.ProviderName = aws.String(d.Get("provider_name").(string)) } if d.HasChange("tags_all") { @@ -177,24 +173,53 @@ func resourcePortfolioUpdate(ctx context.Context, d *schema.ResourceData, meta i input.RemoveTags = aws.StringSlice(tftags.New(o).IgnoreAWS().Keys()) } - log.Printf("[DEBUG] Update Service Catalog Portfolio: %#v", input) - _, err := conn.UpdatePortfolioWithContext(ctx, &input) + _, err := conn.UpdatePortfolioWithContext(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Updating Service Catalog Portfolio '%s' failed: %s", *input.Id, err.Error()) + return sdkdiag.AppendErrorf(diags, "updating Service Catalog Portfolio (%s): %s", d.Id(), err) } + return append(diags, resourcePortfolioRead(ctx, d, meta)...) } func resourcePortfolioDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).ServiceCatalogConn() - input := servicecatalog.DeletePortfolioInput{} - input.Id = aws.String(d.Id()) - log.Printf("[DEBUG] Delete Service Catalog Portfolio: %#v", input) - _, err := conn.DeletePortfolioWithContext(ctx, &input) + log.Printf("[DEBUG] Deleting Service Catalog Portfolio: %s", d.Id()) + _, err := conn.DeletePortfolioWithContext(ctx, &servicecatalog.DeletePortfolioInput{ + Id: aws.String(d.Id()), + }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Deleting Service Catalog Portfolio '%s' failed: %s", *input.Id, err.Error()) + return sdkdiag.AppendErrorf(diags, "deleting Service Catalog Portfolio (%s): %s", d.Id(), err) } + return diags } + +func FindPortfolioByID(ctx context.Context, conn *servicecatalog.ServiceCatalog, id string) (*servicecatalog.DescribePortfolioOutput, error) { + input := &servicecatalog.DescribePortfolioInput{ + AcceptLanguage: aws.String(AcceptLanguageEnglish), + Id: aws.String(id), + } + + output, err := conn.DescribePortfolioWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, servicecatalog.ErrCodeResourceNotFoundException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output, nil +} diff --git a/internal/service/servicecatalog/portfolio_test.go b/internal/service/servicecatalog/portfolio_test.go index e46add375eb..3f6adec88b4 100644 --- a/internal/service/servicecatalog/portfolio_test.go +++ b/internal/service/servicecatalog/portfolio_test.go @@ -6,13 +6,14 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/servicecatalog" 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" + tfservicecatalog "github.com/hashicorp/terraform-provider-aws/internal/service/servicecatalog" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccServiceCatalogPortfolio_basic(t *testing.T) { @@ -30,7 +31,7 @@ func TestAccServiceCatalogPortfolio_basic(t *testing.T) { { Config: testAccPortfolioConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckPortfolio(ctx, resourceName, &dpo), + testAccCheckPortfolioExists(ctx, resourceName, &dpo), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "catalog", regexp.MustCompile(`portfolio/.+`)), resource.TestCheckResourceAttrSet(resourceName, "created_time"), resource.TestCheckResourceAttr(resourceName, "name", name), @@ -63,8 +64,8 @@ func TestAccServiceCatalogPortfolio_disappears(t *testing.T) { { Config: testAccPortfolioConfig_basic(name), Check: resource.ComposeTestCheckFunc( - testAccCheckPortfolio(ctx, resourceName, &dpo), - testAccCheckServiceCatlaogPortfolioDisappears(ctx, &dpo), + testAccCheckPortfolioExists(ctx, resourceName, &dpo), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfservicecatalog.ResourcePortfolio(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -87,7 +88,7 @@ func TestAccServiceCatalogPortfolio_tags(t *testing.T) { { Config: testAccPortfolioConfig_tags1(name, "key1", "value1"), Check: resource.ComposeTestCheckFunc( - testAccCheckPortfolio(ctx, resourceName, &dpo), + testAccCheckPortfolioExists(ctx, resourceName, &dpo), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), @@ -101,7 +102,7 @@ func TestAccServiceCatalogPortfolio_tags(t *testing.T) { { Config: testAccPortfolioConfig_tags2(name, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckPortfolio(ctx, resourceName, &dpo), + testAccCheckPortfolioExists(ctx, resourceName, &dpo), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), @@ -111,7 +112,7 @@ func TestAccServiceCatalogPortfolio_tags(t *testing.T) { { Config: testAccPortfolioConfig_tags1(name, "key2", "value2"), Check: resource.ComposeTestCheckFunc( - testAccCheckPortfolio(ctx, resourceName, &dpo), + testAccCheckPortfolioExists(ctx, resourceName, &dpo), resource.TestCheckResourceAttr(resourceName, "name", name), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), @@ -121,40 +122,28 @@ func TestAccServiceCatalogPortfolio_tags(t *testing.T) { }) } -func testAccCheckPortfolio(ctx context.Context, pr string, dpo *servicecatalog.DescribePortfolioOutput) resource.TestCheckFunc { +func testAccCheckPortfolioExists(ctx context.Context, n string, v *servicecatalog.DescribePortfolioOutput) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ServiceCatalogConn() - 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 Service Catalog Portfolio ID is set") } - input := servicecatalog.DescribePortfolioInput{} - input.Id = aws.String(rs.Primary.ID) + conn := acctest.Provider.Meta().(*conns.AWSClient).ServiceCatalogConn() + + output, err := tfservicecatalog.FindPortfolioByID(ctx, conn, rs.Primary.ID) - resp, err := conn.DescribePortfolioWithContext(ctx, &input) if err != nil { return err } - *dpo = *resp - return nil - } -} - -func testAccCheckServiceCatlaogPortfolioDisappears(ctx context.Context, dpo *servicecatalog.DescribePortfolioOutput) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ServiceCatalogConn() - - input := servicecatalog.DeletePortfolioInput{} - input.Id = dpo.PortfolioDetail.Id + *v = *output - _, err := conn.DeletePortfolioWithContext(ctx, &input) - return err + return nil } } @@ -166,13 +155,18 @@ func testAccCheckServiceCatlaogPortfolioDestroy(ctx context.Context) resource.Te if rs.Type != "aws_servicecatalog_portfolio" { continue } - input := servicecatalog.DescribePortfolioInput{} - input.Id = aws.String(rs.Primary.ID) - _, err := conn.DescribePortfolioWithContext(ctx, &input) - if err == nil { - return fmt.Errorf("Portfolio still exists") + _, err := tfservicecatalog.FindPortfolioByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err } + + return fmt.Errorf("Service Catalog Portfolio %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/ses/receipt_rule.go b/internal/service/ses/receipt_rule.go index e308972da37..4f334b590ae 100644 --- a/internal/service/ses/receipt_rule.go +++ b/internal/service/ses/receipt_rule.go @@ -1,7 +1,6 @@ package ses import ( - "bytes" "context" "fmt" "log" @@ -14,12 +13,13 @@ import ( "github.com/aws/aws-sdk-go/service/ses" "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/create" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/flex" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" ) @@ -29,63 +29,12 @@ func ResourceReceiptRule() *schema.Resource { UpdateWithoutTimeout: resourceReceiptRuleUpdate, ReadWithoutTimeout: resourceReceiptRuleRead, DeleteWithoutTimeout: resourceReceiptRuleDelete, + Importer: &schema.ResourceImporter{ StateContext: resourceReceiptRuleImport, }, Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Computed: true, - }, - "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - ValidateFunc: validation.All( - validation.StringLenBetween(1, 64), - validation.StringMatch(regexp.MustCompile(`^[0-9a-zA-Z._-]+$`), "must contain only alphanumeric, period, underscore, and hyphen characters"), - validation.StringMatch(regexp.MustCompile(`^[0-9a-zA-Z]`), "must begin with a alphanumeric character"), - validation.StringMatch(regexp.MustCompile(`[0-9a-zA-Z]$`), "must end with a alphanumeric character"), - ), - }, - - "rule_set_name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - - "after": { - Type: schema.TypeString, - Optional: true, - }, - - "enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "recipients": { - Type: schema.TypeSet, - Elem: &schema.Schema{Type: schema.TypeString}, - Optional: true, - }, - - "scan_enabled": { - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - - "tls_policy": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice(ses.TlsPolicy_Values(), false), - }, - "add_header_action": { Type: schema.TypeSet, Optional: true, @@ -99,30 +48,26 @@ func ResourceReceiptRule() *schema.Resource { validation.StringMatch(regexp.MustCompile(`^[0-9a-zA-Z-]+$`), "must contain only alphanumeric and dash characters"), ), }, - "header_value": { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringLenBetween(0, 2048), }, - "position": { Type: schema.TypeInt, Required: true, }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["header_name"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["header_value"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["position"].(int))) - - return create.StringHashcode(buf.String()) - }, }, - + "after": { + Type: schema.TypeString, + Optional: true, + }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, "bounce_action": { Type: schema.TypeSet, Optional: true, @@ -132,55 +77,35 @@ func ResourceReceiptRule() *schema.Resource { Type: schema.TypeString, Required: true, }, - + "position": { + Type: schema.TypeInt, + Required: true, + }, "sender": { Type: schema.TypeString, Required: true, }, - "smtp_reply_code": { Type: schema.TypeString, Required: true, }, - "status_code": { Type: schema.TypeString, Optional: true, }, - "topic_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - - "position": { - Type: schema.TypeInt, - Required: true, - }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["message"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["sender"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["smtp_reply_code"].(string))) - - if _, ok := m["status_code"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["status_code"].(string))) - } - - if _, ok := m["topic_arn"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["topic_arn"].(string))) - } - - buf.WriteString(fmt.Sprintf("%d-", m["position"].(int))) - - return create.StringHashcode(buf.String()) - }, }, - + "enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "lambda_action": { Type: schema.TypeSet, Optional: true, @@ -191,45 +116,45 @@ func ResourceReceiptRule() *schema.Resource { Required: true, ValidateFunc: verify.ValidARN, }, - "invocation_type": { Type: schema.TypeString, Optional: true, Default: ses.InvocationTypeEvent, ValidateFunc: validation.StringInSlice(ses.InvocationType_Values(), false), }, - + "position": { + Type: schema.TypeInt, + Required: true, + }, "topic_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - - "position": { - Type: schema.TypeInt, - Required: true, - }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["function_arn"].(string))) - - if _, ok := m["invocation_type"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["invocation_type"].(string))) - } - - if _, ok := m["topic_arn"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["topic_arn"].(string))) - } - - buf.WriteString(fmt.Sprintf("%d-", m["position"].(int))) - - return create.StringHashcode(buf.String()) - }, }, - + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: validation.All( + validation.StringLenBetween(1, 64), + validation.StringMatch(regexp.MustCompile(`^[0-9a-zA-Z._-]+$`), "must contain only alphanumeric, period, underscore, and hyphen characters"), + validation.StringMatch(regexp.MustCompile(`^[0-9a-zA-Z]`), "must begin with a alphanumeric character"), + validation.StringMatch(regexp.MustCompile(`[0-9a-zA-Z]$`), "must end with a alphanumeric character"), + ), + }, + "recipients": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + }, + "rule_set_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, "s3_action": { Type: schema.TypeSet, Optional: true, @@ -239,54 +164,33 @@ func ResourceReceiptRule() *schema.Resource { Type: schema.TypeString, Required: true, }, - "kms_key_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - "object_key_prefix": { Type: schema.TypeString, Optional: true, }, - - "topic_arn": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: verify.ValidARN, - }, - "position": { Type: schema.TypeInt, Required: true, ValidateFunc: validation.IntAtLeast(1), }, + "topic_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidARN, + }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["bucket_name"].(string))) - - if _, ok := m["kms_key_arn"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["kms_key_arn"].(string))) - } - - if _, ok := m["object_key_prefix"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["object_key_prefix"].(string))) - } - - if _, ok := m["topic_arn"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["topic_arn"].(string))) - } - - buf.WriteString(fmt.Sprintf("%d-", m["position"].(int))) - - return create.StringHashcode(buf.String()) - }, }, - + "scan_enabled": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, "sns_action": { Type: schema.TypeSet, Optional: true, @@ -298,29 +202,18 @@ func ResourceReceiptRule() *schema.Resource { Optional: true, ValidateFunc: validation.StringInSlice(ses.SNSActionEncoding_Values(), false), }, + "position": { + Type: schema.TypeInt, + Required: true, + }, "topic_arn": { Type: schema.TypeString, Required: true, ValidateFunc: verify.ValidARN, }, - - "position": { - Type: schema.TypeInt, - Required: true, - }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["encoding"].(string))) - buf.WriteString(fmt.Sprintf("%s-", m["topic_arn"].(string))) - buf.WriteString(fmt.Sprintf("%d-", m["position"].(int))) - - return create.StringHashcode(buf.String()) - }, }, - "stop_action": { Type: schema.TypeSet, Optional: true, @@ -331,34 +224,24 @@ func ResourceReceiptRule() *schema.Resource { Required: true, ValidateFunc: validation.StringInSlice(ses.StopScope_Values(), false), }, - + "position": { + Type: schema.TypeInt, + Required: true, + }, "topic_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - - "position": { - Type: schema.TypeInt, - Required: true, - }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["scope"].(string))) - - if _, ok := m["topic_arn"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["topic_arn"].(string))) - } - - buf.WriteString(fmt.Sprintf("%d-", m["position"].(int))) - - return create.StringHashcode(buf.String()) - }, }, - + "tls_policy": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateFunc: validation.StringInSlice(ses.TlsPolicy_Values(), false), + }, "workmail_action": { Type: schema.TypeSet, Optional: true, @@ -369,102 +252,43 @@ func ResourceReceiptRule() *schema.Resource { Required: true, ValidateFunc: verify.ValidARN, }, - + "position": { + Type: schema.TypeInt, + Required: true, + }, "topic_arn": { Type: schema.TypeString, Optional: true, ValidateFunc: verify.ValidARN, }, - - "position": { - Type: schema.TypeInt, - Required: true, - }, }, }, - Set: func(v interface{}) int { - var buf bytes.Buffer - m := v.(map[string]interface{}) - buf.WriteString(fmt.Sprintf("%s-", m["organization_arn"].(string))) - - if _, ok := m["topic_arn"]; ok { - buf.WriteString(fmt.Sprintf("%s-", m["topic_arn"].(string))) - } - - buf.WriteString(fmt.Sprintf("%d-", m["position"].(int))) - - return create.StringHashcode(buf.String()) - }, }, }, } } -func resourceReceiptRuleImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - idParts := strings.Split(d.Id(), ":") - if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { - return nil, fmt.Errorf("unexpected format of ID (%q), expected :", d.Id()) - } - - ruleSetName := idParts[0] - ruleName := idParts[1] - - d.Set("rule_set_name", ruleSetName) - d.Set("name", ruleName) - d.SetId(ruleName) - - return []*schema.ResourceData{d}, nil -} - func resourceReceiptRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SESConn() - createOpts := &ses.CreateReceiptRuleInput{ + name := d.Get("name").(string) + input := &ses.CreateReceiptRuleInput{ Rule: buildReceiptRule(d), RuleSetName: aws.String(d.Get("rule_set_name").(string)), } if v, ok := d.GetOk("after"); ok { - createOpts.After = aws.String(v.(string)) - } - - _, err := conn.CreateReceiptRuleWithContext(ctx, createOpts) - if err != nil { - return sdkdiag.AppendErrorf(diags, "creating SES rule: %s", err) + input.After = aws.String(v.(string)) } - d.SetId(d.Get("name").(string)) - - return append(diags, resourceReceiptRuleRead(ctx, d, meta)...) -} - -func resourceReceiptRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).SESConn() + _, err := conn.CreateReceiptRuleWithContext(ctx, input) - updateOpts := &ses.UpdateReceiptRuleInput{ - Rule: buildReceiptRule(d), - RuleSetName: aws.String(d.Get("rule_set_name").(string)), - } - - _, err := conn.UpdateReceiptRuleWithContext(ctx, updateOpts) if err != nil { - return sdkdiag.AppendErrorf(diags, "updating SES rule: %s", err) + return sdkdiag.AppendErrorf(diags, "creating SES Receipt Rule (%s): %s", name, err) } - if d.HasChange("after") { - changePosOpts := &ses.SetReceiptRulePositionInput{ - After: aws.String(d.Get("after").(string)), - RuleName: aws.String(d.Get("name").(string)), - RuleSetName: aws.String(d.Get("rule_set_name").(string)), - } - - _, err := conn.SetReceiptRulePositionWithContext(ctx, changePosOpts) - if err != nil { - return sdkdiag.AppendErrorf(diags, "updating SES rule: %s", err) - } - } + d.SetId(name) return append(diags, resourceReceiptRuleRead(ctx, d, meta)...) } @@ -474,30 +298,22 @@ func resourceReceiptRuleRead(ctx context.Context, d *schema.ResourceData, meta i conn := meta.(*conns.AWSClient).SESConn() ruleSetName := d.Get("rule_set_name").(string) - describeOpts := &ses.DescribeReceiptRuleInput{ - RuleName: aws.String(d.Id()), - RuleSetName: aws.String(ruleSetName), + rule, err := FindReceiptRuleByTwoPartKey(ctx, conn, d.Id(), ruleSetName) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] SES Receipt Rule (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - response, err := conn.DescribeReceiptRuleWithContext(ctx, describeOpts) if err != nil { - if tfawserr.ErrCodeEquals(err, ses.ErrCodeRuleDoesNotExistException) { - log.Printf("[WARN] SES Receipt Rule (%s) not found", d.Id()) - d.SetId("") - return diags - } - if tfawserr.ErrCodeEquals(err, ses.ErrCodeRuleSetDoesNotExistException) { - log.Printf("[WARN] SES Receipt Rule Set (%s) belonging to SES Receipt Rule (%s) not found, removing from state", aws.StringValue(describeOpts.RuleSetName), d.Id()) - d.SetId("") - return diags - } return sdkdiag.AppendErrorf(diags, "reading SES Receipt Rule (%s): %s", d.Id(), err) } - d.Set("enabled", response.Rule.Enabled) - d.Set("recipients", flex.FlattenStringSet(response.Rule.Recipients)) - d.Set("scan_enabled", response.Rule.ScanEnabled) - d.Set("tls_policy", response.Rule.TlsPolicy) + d.Set("enabled", rule.Enabled) + d.Set("recipients", flex.FlattenStringSet(rule.Recipients)) + d.Set("scan_enabled", rule.ScanEnabled) + d.Set("tls_policy", rule.TlsPolicy) addHeaderActionList := []map[string]interface{}{} bounceActionList := []map[string]interface{}{} @@ -507,7 +323,7 @@ func resourceReceiptRuleRead(ctx context.Context, d *schema.ResourceData, meta i stopActionList := []map[string]interface{}{} workmailActionList := []map[string]interface{}{} - for i, element := range response.Rule.Actions { + for i, element := range rule.Actions { if element.AddHeaderAction != nil { addHeaderAction := map[string]interface{}{ "header_name": aws.StringValue(element.AddHeaderAction.HeaderName), @@ -658,23 +474,97 @@ func resourceReceiptRuleRead(ctx context.Context, d *schema.ResourceData, meta i return diags } +func resourceReceiptRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).SESConn() + + input := &ses.UpdateReceiptRuleInput{ + Rule: buildReceiptRule(d), + RuleSetName: aws.String(d.Get("rule_set_name").(string)), + } + + _, err := conn.UpdateReceiptRuleWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "updating SES Receipt Rule (%s): %s", d.Id(), err) + } + + if d.HasChange("after") { + input := &ses.SetReceiptRulePositionInput{ + After: aws.String(d.Get("after").(string)), + RuleName: aws.String(d.Get("name").(string)), + RuleSetName: aws.String(d.Get("rule_set_name").(string)), + } + + _, err := conn.SetReceiptRulePositionWithContext(ctx, input) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "setting SES Receipt Rule (%s) position: %s", d.Id(), err) + } + } + + return append(diags, resourceReceiptRuleRead(ctx, d, meta)...) +} + func resourceReceiptRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).SESConn() - deleteOpts := &ses.DeleteReceiptRuleInput{ + log.Printf("[DEBUG] Deleting SES Receipt Rule: %s", d.Id()) + _, err := conn.DeleteReceiptRuleWithContext(ctx, &ses.DeleteReceiptRuleInput{ RuleName: aws.String(d.Id()), RuleSetName: aws.String(d.Get("rule_set_name").(string)), - } + }) - _, err := conn.DeleteReceiptRuleWithContext(ctx, deleteOpts) if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting SES receipt rule: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting SES Receipt Rule (%s): %s", d.Id(), err) } return diags } +func resourceReceiptRuleImport(_ context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + idParts := strings.Split(d.Id(), ":") + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + return nil, fmt.Errorf("unexpected format of ID (%q), expected :", d.Id()) + } + + ruleSetName := idParts[0] + ruleName := idParts[1] + + d.Set("rule_set_name", ruleSetName) + d.Set("name", ruleName) + d.SetId(ruleName) + + return []*schema.ResourceData{d}, nil +} + +func FindReceiptRuleByTwoPartKey(ctx context.Context, conn *ses.SES, ruleName, ruleSetName string) (*ses.ReceiptRule, error) { + input := &ses.DescribeReceiptRuleInput{ + RuleName: aws.String(ruleName), + RuleSetName: aws.String(ruleSetName), + } + + output, err := conn.DescribeReceiptRuleWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, ses.ErrCodeRuleDoesNotExistException, ses.ErrCodeRuleSetDoesNotExistException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil || output.Rule == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.Rule, nil +} + func buildReceiptRule(d *schema.ResourceData) *ses.ReceiptRule { receiptRule := &ses.ReceiptRule{ Name: aws.String(d.Get("name").(string)), diff --git a/internal/service/ses/receipt_rule_test.go b/internal/service/ses/receipt_rule_test.go index 27d272918e8..8e538c45a55 100644 --- a/internal/service/ses/receipt_rule_test.go +++ b/internal/service/ses/receipt_rule_test.go @@ -6,7 +6,6 @@ import ( "testing" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/ses" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" sdkacctest "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" @@ -15,12 +14,12 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfses "github.com/hashicorp/terraform-provider-aws/internal/service/ses" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccSESReceiptRule_basic(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -67,7 +66,6 @@ func TestAccSESReceiptRule_basic(t *testing.T) { func TestAccSESReceiptRule_s3Action(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -105,7 +103,6 @@ func TestAccSESReceiptRule_s3Action(t *testing.T) { func TestAccSESReceiptRule_snsAction(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -143,7 +140,6 @@ func TestAccSESReceiptRule_snsAction(t *testing.T) { func TestAccSESReceiptRule_snsActionEncoding(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -181,7 +177,6 @@ func TestAccSESReceiptRule_snsActionEncoding(t *testing.T) { func TestAccSESReceiptRule_lambdaAction(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -219,7 +214,6 @@ func TestAccSESReceiptRule_lambdaAction(t *testing.T) { func TestAccSESReceiptRule_stopAction(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -256,7 +250,6 @@ func TestAccSESReceiptRule_stopAction(t *testing.T) { func TestAccSESReceiptRule_order(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -290,7 +283,6 @@ func TestAccSESReceiptRule_order(t *testing.T) { func TestAccSESReceiptRule_actions(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" @@ -332,10 +324,8 @@ func TestAccSESReceiptRule_actions(t *testing.T) { func TestAccSESReceiptRule_disappears(t *testing.T) { ctx := acctest.Context(t) var rule ses.ReceiptRule - rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_ses_receipt_rule.test" - ruleSetResourceName := "aws_ses_receipt_rule_set.test" resource.ParallelTest(t, resource.TestCase{ @@ -377,51 +367,43 @@ func testAccCheckReceiptRuleDestroy(ctx context.Context) resource.TestCheckFunc continue } - params := &ses.DescribeReceiptRuleInput{ - RuleName: aws.String(rs.Primary.Attributes["name"]), - RuleSetName: aws.String(rs.Primary.Attributes["rule_set_name"]), - } + _, err := tfses.FindReceiptRuleByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["rule_set_name"]) - _, err := conn.DescribeReceiptRuleWithContext(ctx, params) - if err == nil { - return fmt.Errorf("Receipt rule %s still exists. Failing!", rs.Primary.ID) + if tfresource.NotFound(err) { + continue } - // Verify the error is what we want - _, ok := err.(awserr.Error) - if !ok { + if err != nil { return err } + + return fmt.Errorf("SES Receipt Rule %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckReceiptRuleExists(ctx context.Context, n string, rule *ses.ReceiptRule) resource.TestCheckFunc { +func testAccCheckReceiptRuleExists(ctx context.Context, n string, v *ses.ReceiptRule) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("SES Receipt Rule not found: %s", n) + return fmt.Errorf("Not found: %s", n) } if rs.Primary.ID == "" { - return fmt.Errorf("SES Receipt Rule name not set") + return fmt.Errorf("No SES Receipt Rule ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).SESConn() - params := &ses.DescribeReceiptRuleInput{ - RuleName: aws.String(rs.Primary.Attributes["name"]), - RuleSetName: aws.String(rs.Primary.Attributes["rule_set_name"]), - } + output, err := tfses.FindReceiptRuleByTwoPartKey(ctx, conn, rs.Primary.ID, rs.Primary.Attributes["rule_set_name"]) - resp, err := conn.DescribeReceiptRuleWithContext(ctx, params) if err != nil { return err } - *rule = *resp.Rule + *v = *output return nil } diff --git a/internal/service/waf/helpers.go b/internal/service/waf/helpers.go index e8edc2fb136..daf54d562e0 100644 --- a/internal/service/waf/helpers.go +++ b/internal/service/waf/helpers.go @@ -14,20 +14,24 @@ import ( func SizeConstraintSetSchema() map[string]*schema.Schema { return map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Computed: true, + }, "name": { Type: schema.TypeString, Required: true, ForceNew: true, }, - "arn": { - Type: schema.TypeString, - Computed: true, - }, "size_constraints": { Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ + "comparison_operator": { + Type: schema.TypeString, + Required: true, + }, "field_to_match": { Type: schema.TypeList, Required: true, @@ -45,10 +49,6 @@ func SizeConstraintSetSchema() map[string]*schema.Schema { }, }, }, - "comparison_operator": { - Type: schema.TypeString, - Required: true, - }, "size": { Type: schema.TypeInt, Required: true, diff --git a/internal/service/waf/rate_based_rule.go b/internal/service/waf/rate_based_rule.go index d53f2938d3a..5469ba69a95 100644 --- a/internal/service/waf/rate_based_rule.go +++ b/internal/service/waf/rate_based_rule.go @@ -7,14 +7,16 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/waf" + "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/errs/sdkdiag" 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" ) @@ -24,15 +26,15 @@ func ResourceRateBasedRule() *schema.Resource { ReadWithoutTimeout: resourceRateBasedRuleRead, UpdateWithoutTimeout: resourceRateBasedRuleUpdate, DeleteWithoutTimeout: resourceRateBasedRuleDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ - "name": { + "arn": { Type: schema.TypeString, - Required: true, - ForceNew: true, + Computed: true, }, "metric_name": { Type: schema.TypeString, @@ -40,20 +42,25 @@ func ResourceRateBasedRule() *schema.Resource { ForceNew: true, ValidateFunc: validMetricName, }, + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + }, "predicates": { Type: schema.TypeSet, Optional: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ - "negated": { - Type: schema.TypeBool, - Required: true, - }, "data_id": { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringLenBetween(0, 128), }, + "negated": { + Type: schema.TypeBool, + Required: true, + }, "type": { Type: schema.TypeString, Required: true, @@ -73,10 +80,6 @@ func ResourceRateBasedRule() *schema.Resource { }, "tags": tftags.TagsSchema(), "tags_all": tftags.TagsSchemaComputed(), - "arn": { - Type: schema.TypeString, - Computed: true, - }, }, CustomizeDiff: verify.SetTagsDiff, @@ -89,34 +92,39 @@ func resourceRateBasedRuleCreate(ctx context.Context, d *schema.ResourceData, me defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig tags := defaultTagsConfig.MergeTags(tftags.New(d.Get("tags").(map[string]interface{}))) + name := d.Get("name").(string) + input := &waf.CreateRateBasedRuleInput{ + MetricName: aws.String(d.Get("metric_name").(string)), + Name: aws.String(name), + RateKey: aws.String(d.Get("rate_key").(string)), + RateLimit: aws.Int64(int64(d.Get("rate_limit").(int))), + } + wr := NewRetryer(conn) - out, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - params := &waf.CreateRateBasedRuleInput{ - ChangeToken: token, - MetricName: aws.String(d.Get("metric_name").(string)), - Name: aws.String(d.Get("name").(string)), - RateKey: aws.String(d.Get("rate_key").(string)), - RateLimit: aws.Int64(int64(d.Get("rate_limit").(int))), - } + outputRaw, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { + input.ChangeToken = token if len(tags) > 0 { - params.Tags = Tags(tags.IgnoreAWS()) + input.Tags = Tags(tags.IgnoreAWS()) } - return conn.CreateRateBasedRuleWithContext(ctx, params) + return conn.CreateRateBasedRuleWithContext(ctx, input) }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating WAF Rate Based Rule (%s): %s", d.Get("name").(string), err) + return sdkdiag.AppendErrorf(diags, "creating WAF Rate Based Rule (%s): %s", name, err) } - resp := out.(*waf.CreateRateBasedRuleOutput) - d.SetId(aws.StringValue(resp.Rule.RuleId)) + + output := outputRaw.(*waf.CreateRateBasedRuleOutput) + + d.SetId(aws.StringValue(output.Rule.RuleId)) newPredicates := d.Get("predicates").(*schema.Set).List() if len(newPredicates) > 0 { - noPredicates := []interface{}{} - err := updateRateBasedRuleResource(ctx, *resp.Rule.RuleId, noPredicates, newPredicates, d.Get("rate_limit"), conn) + err := updateRateBasedRuleResource(ctx, conn, aws.StringValue(output.Rule.RuleId), []interface{}{}, newPredicates, d.Get("rate_limit")) + if err != nil { - return sdkdiag.AppendErrorf(diags, "updating WAF Rate Based Rule: %s", err) + return sdkdiag.AppendErrorf(diags, "updating WAF Rate Based Rule (%s): %s", d.Id(), err) } } @@ -129,24 +137,33 @@ func resourceRateBasedRuleRead(ctx context.Context, d *schema.ResourceData, meta defaultTagsConfig := meta.(*conns.AWSClient).DefaultTagsConfig ignoreTagsConfig := meta.(*conns.AWSClient).IgnoreTagsConfig - params := &waf.GetRateBasedRuleInput{ - RuleId: aws.String(d.Id()), + rule, err := FindRateBasedRuleByID(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] WAF Rate Based Rule (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - resp, err := conn.GetRateBasedRuleWithContext(ctx, params) if err != nil { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == waf.ErrCodeNonexistentItemException { - log.Printf("[WARN] WAF Rate Based Rule (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } - - return sdkdiag.AppendErrorf(diags, "reading WAF Rate Based Rule (%s): %s", d.Get("name").(string), err) + return sdkdiag.AppendErrorf(diags, "reading WAF Rate Based Rule (%s): %s", d.Id(), err) } + arn := arn.ARN{ + Partition: meta.(*conns.AWSClient).Partition, + Service: "waf", + AccountID: meta.(*conns.AWSClient).AccountID, + Resource: fmt.Sprintf("ratebasedrule/%s", d.Id()), + }.String() + d.Set("arn", arn) + d.Set("name", rule.Name) + d.Set("metric_name", rule.MetricName) + d.Set("rate_key", rule.RateKey) + d.Set("rate_limit", rule.RateLimit) + var predicates []map[string]interface{} - for _, predicateSet := range resp.Rule.MatchPredicates { + for _, predicateSet := range rule.MatchPredicates { predicate := map[string]interface{}{ "negated": *predicateSet.Negated, "type": *predicateSet.Type, @@ -155,20 +172,17 @@ func resourceRateBasedRuleRead(ctx context.Context, d *schema.ResourceData, meta predicates = append(predicates, predicate) } - arn := arn.ARN{ - Partition: meta.(*conns.AWSClient).Partition, - Service: "waf", - AccountID: meta.(*conns.AWSClient).AccountID, - Resource: fmt.Sprintf("ratebasedrule/%s", d.Id()), - }.String() - d.Set("arn", arn) + if err := d.Set("predicates", predicates); err != nil { + return sdkdiag.AppendErrorf(diags, "setting predicates: %s", err) + } + + tags, err := ListTags(ctx, conn, arn) - tagList, err := ListTags(ctx, conn, arn) if err != nil { - return sdkdiag.AppendErrorf(diags, "to get WAF Rated Based Rule parameter tags for %s: %s", d.Get("name"), err) + return sdkdiag.AppendErrorf(diags, "listing tags for WAF Rate Based Rule (%s): %s", arn, err) } - tags := tagList.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) + tags = tags.IgnoreAWS().IgnoreConfig(ignoreTagsConfig) //lintignore:AWSR002 if err := d.Set("tags", tags.RemoveDefaultConfig(defaultTagsConfig).Map()); err != nil { @@ -179,12 +193,6 @@ func resourceRateBasedRuleRead(ctx context.Context, d *schema.ResourceData, meta return sdkdiag.AppendErrorf(diags, "setting tags_all: %s", err) } - d.Set("predicates", predicates) - d.Set("name", resp.Rule.Name) - d.Set("metric_name", resp.Rule.MetricName) - d.Set("rate_key", resp.Rule.RateKey) - d.Set("rate_limit", resp.Rule.RateLimit) - return diags } @@ -197,9 +205,10 @@ func resourceRateBasedRuleUpdate(ctx context.Context, d *schema.ResourceData, me oldP, newP := o.(*schema.Set).List(), n.(*schema.Set).List() rateLimit := d.Get("rate_limit") - err := updateRateBasedRuleResource(ctx, d.Id(), oldP, newP, rateLimit, conn) + err := updateRateBasedRuleResource(ctx, conn, d.Id(), oldP, newP, rateLimit) + if err != nil { - return sdkdiag.AppendErrorf(diags, "updating WAF Rule: %s", err) + return sdkdiag.AppendErrorf(diags, "updating WAF Rate Based Rule (%s): %s", d.Id(), err) } } @@ -223,43 +232,67 @@ func resourceRateBasedRuleDelete(ctx context.Context, d *schema.ResourceData, me noPredicates := []interface{}{} rateLimit := d.Get("rate_limit") - err := updateRateBasedRuleResource(ctx, d.Id(), oldPredicates, noPredicates, rateLimit, conn) + err := updateRateBasedRuleResource(ctx, conn, d.Id(), oldPredicates, noPredicates, rateLimit) + if err != nil { - return sdkdiag.AppendErrorf(diags, "updating WAF Rate Based Rule Predicates: %s", err) + return sdkdiag.AppendErrorf(diags, "updating WAF Rate Based Rule (%s): %s", d.Id(), err) } } + log.Printf("[INFO] Deleting WAF Rate Based Rule: %s", d.Id()) wr := NewRetryer(conn) _, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - req := &waf.DeleteRateBasedRuleInput{ + return conn.DeleteRateBasedRuleWithContext(ctx, &waf.DeleteRateBasedRuleInput{ ChangeToken: token, RuleId: aws.String(d.Id()), - } - log.Printf("[INFO] Deleting WAF Rate Based Rule") - return conn.DeleteRateBasedRuleWithContext(ctx, req) + }) }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting WAF Rate Based Rule: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting WAF Rate Based Rule (%s): %s", d.Id(), err) } return diags } -func updateRateBasedRuleResource(ctx context.Context, id string, oldP, newP []interface{}, rateLimit interface{}, conn *waf.WAF) error { +func updateRateBasedRuleResource(ctx context.Context, conn *waf.WAF, id string, oldP, newP []interface{}, rateLimit interface{}) error { + input := &waf.UpdateRateBasedRuleInput{ + RateLimit: aws.Int64(int64(rateLimit.(int))), + RuleId: aws.String(id), + Updates: DiffRulePredicates(oldP, newP), + } + wr := NewRetryer(conn) _, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - req := &waf.UpdateRateBasedRuleInput{ - ChangeToken: token, - RuleId: aws.String(id), - Updates: DiffRulePredicates(oldP, newP), - RateLimit: aws.Int64(int64(rateLimit.(int))), - } + input.ChangeToken = token - return conn.UpdateRateBasedRuleWithContext(ctx, req) + return conn.UpdateRateBasedRuleWithContext(ctx, input) }) + + return err +} + +func FindRateBasedRuleByID(ctx context.Context, conn *waf.WAF, id string) (*waf.RateBasedRule, error) { + input := &waf.GetRateBasedRuleInput{ + RuleId: aws.String(id), + } + + output, err := conn.GetRateBasedRuleWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, waf.ErrCodeNonexistentItemException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { - return fmt.Errorf("updating WAF Rate Based Rule: %s", err) + return nil, err + } + + if output == nil || output.Rule == nil { + return nil, tfresource.NewEmptyResultError(input) } - return nil + return output.Rule, nil } diff --git a/internal/service/waf/rate_based_rule_test.go b/internal/service/waf/rate_based_rule_test.go index d3ae6eee180..31b33033f87 100644 --- a/internal/service/waf/rate_based_rule_test.go +++ b/internal/service/waf/rate_based_rule_test.go @@ -6,15 +6,14 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/waf" - "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" tfwaf "github.com/hashicorp/terraform-provider-aws/internal/service/waf" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccWAFRateBasedRule_basic(t *testing.T) { @@ -300,21 +299,18 @@ func testAccCheckRateBasedRuleDestroy(ctx context.Context) resource.TestCheckFun } conn := acctest.Provider.Meta().(*conns.AWSClient).WAFConn() - resp, err := conn.GetRateBasedRuleWithContext(ctx, &waf.GetRateBasedRuleInput{ - RuleId: aws.String(rs.Primary.ID), - }) - - if err == nil { - if *resp.Rule.RuleId == rs.Primary.ID { - return fmt.Errorf("WAF Rule %s still exists", rs.Primary.ID) - } - } - if tfawserr.ErrCodeEquals(err, waf.ErrCodeNonexistentItemException) { + _, err := tfwaf.FindRateBasedRuleByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } - return err + if err != nil { + return err + } + + return fmt.Errorf("WAF Rate Based Rule %s still exists", rs.Primary.ID) } return nil @@ -329,24 +325,20 @@ func testAccCheckRateBasedRuleExists(ctx context.Context, n string, v *waf.RateB } if rs.Primary.ID == "" { - return fmt.Errorf("No WAF Rule ID is set") + return fmt.Errorf("No WAF Rate Based Rule ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).WAFConn() - resp, err := conn.GetRateBasedRuleWithContext(ctx, &waf.GetRateBasedRuleInput{ - RuleId: aws.String(rs.Primary.ID), - }) + + output, err := tfwaf.FindRateBasedRuleByID(ctx, conn, rs.Primary.ID) if err != nil { return err } - if *resp.Rule.RuleId == rs.Primary.ID { - *v = *resp.Rule - return nil - } + *v = *output - return fmt.Errorf("WAF Rule (%s) not found", rs.Primary.ID) + return nil } } diff --git a/internal/service/waf/size_constraint_set.go b/internal/service/waf/size_constraint_set.go index 1ac23e3da52..53b3f516bd5 100644 --- a/internal/service/waf/size_constraint_set.go +++ b/internal/service/waf/size_constraint_set.go @@ -7,12 +7,14 @@ import ( "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/arn" - "github.com/aws/aws-sdk-go/aws/awserr" "github.com/aws/aws-sdk-go/service/waf" + "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/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func ResourceSizeConstraintSet() *schema.Resource { @@ -21,6 +23,7 @@ func ResourceSizeConstraintSet() *schema.Resource { ReadWithoutTimeout: resourceSizeConstraintSetRead, UpdateWithoutTimeout: resourceSizeConstraintSetUpdate, DeleteWithoutTimeout: resourceSizeConstraintSetDelete, + Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, }, @@ -33,23 +36,23 @@ func resourceSizeConstraintSetCreate(ctx context.Context, d *schema.ResourceData var diags diag.Diagnostics conn := meta.(*conns.AWSClient).WAFConn() - log.Printf("[INFO] Creating SizeConstraintSet: %s", d.Get("name").(string)) + name := d.Get("name").(string) + input := &waf.CreateSizeConstraintSetInput{ + Name: aws.String(name), + } wr := NewRetryer(conn) - out, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - params := &waf.CreateSizeConstraintSetInput{ - ChangeToken: token, - Name: aws.String(d.Get("name").(string)), - } + outputRaw, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { + input.ChangeToken = token - return conn.CreateSizeConstraintSetWithContext(ctx, params) + return conn.CreateSizeConstraintSetWithContext(ctx, input) }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating SizeConstraintSet: %s", err) + return sdkdiag.AppendErrorf(diags, "creating WAF Size Constraint Set (%s): %s", name, err) } - resp := out.(*waf.CreateSizeConstraintSetOutput) - d.SetId(aws.StringValue(resp.SizeConstraintSet.SizeConstraintSetId)) + d.SetId(aws.StringValue(outputRaw.(*waf.CreateSizeConstraintSetOutput).SizeConstraintSet.SizeConstraintSetId)) return append(diags, resourceSizeConstraintSetUpdate(ctx, d, meta)...) } @@ -57,24 +60,18 @@ func resourceSizeConstraintSetCreate(ctx context.Context, d *schema.ResourceData func resourceSizeConstraintSetRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics conn := meta.(*conns.AWSClient).WAFConn() - log.Printf("[INFO] Reading SizeConstraintSet: %s", d.Get("name").(string)) - params := &waf.GetSizeConstraintSetInput{ - SizeConstraintSetId: aws.String(d.Id()), - } - resp, err := conn.GetSizeConstraintSetWithContext(ctx, params) - if err != nil { - if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == waf.ErrCodeNonexistentItemException { - log.Printf("[WARN] WAF SizeConstraintSet (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } + sizeConstraintSet, err := FindSizeConstraintSetByID(ctx, conn, d.Id()) - return sdkdiag.AppendErrorf(diags, "reading WAF Size Constraint Set (%s): %s", d.Get("name").(string), err) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] WAF Size Constraint Set (%s) not found, removing from state", d.Id()) + d.SetId("") + return diags } - d.Set("name", resp.SizeConstraintSet.Name) - d.Set("size_constraints", FlattenSizeConstraints(resp.SizeConstraintSet.SizeConstraints)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading WAF Size Constraint Set (%s): %s", d.Id(), err) + } arn := arn.ARN{ Partition: meta.(*conns.AWSClient).Partition, @@ -83,6 +80,10 @@ func resourceSizeConstraintSetRead(ctx context.Context, d *schema.ResourceData, Resource: fmt.Sprintf("sizeconstraintset/%s", d.Id()), } d.Set("arn", arn.String()) + d.Set("name", sizeConstraintSet.Name) + if err := d.Set("size_constraints", FlattenSizeConstraints(sizeConstraintSet.SizeConstraints)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting size_constraints: %s", err) + } return diags } @@ -95,9 +96,10 @@ func resourceSizeConstraintSetUpdate(ctx context.Context, d *schema.ResourceData o, n := d.GetChange("size_constraints") oldConstraints, newConstraints := o.(*schema.Set).List(), n.(*schema.Set).List() - err := updateSizeConstraintSetResource(ctx, d.Id(), oldConstraints, newConstraints, conn) + err := updateSizeConstraintSetResource(ctx, conn, d.Id(), oldConstraints, newConstraints) + if err != nil { - return sdkdiag.AppendErrorf(diags, "updating SizeConstraintSet: %s", err) + return sdkdiag.AppendErrorf(diags, "updating WAF Size Constraint Set (%s): %s", d.Id(), err) } } @@ -111,43 +113,65 @@ func resourceSizeConstraintSetDelete(ctx context.Context, d *schema.ResourceData oldConstraints := d.Get("size_constraints").(*schema.Set).List() if len(oldConstraints) > 0 { - noConstraints := []interface{}{} - err := updateSizeConstraintSetResource(ctx, d.Id(), oldConstraints, noConstraints, conn) + err := updateSizeConstraintSetResource(ctx, conn, d.Id(), oldConstraints, []interface{}{}) + if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting SizeConstraintSet: %s", err) + return sdkdiag.AppendErrorf(diags, "updating WAF Size Constraint Set (%s): %s", d.Id(), err) } } wr := NewRetryer(conn) _, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - req := &waf.DeleteSizeConstraintSetInput{ + return conn.DeleteSizeConstraintSetWithContext(ctx, &waf.DeleteSizeConstraintSetInput{ ChangeToken: token, SizeConstraintSetId: aws.String(d.Id()), - } - return conn.DeleteSizeConstraintSetWithContext(ctx, req) + }) }) + if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting SizeConstraintSet: %s", err) + return sdkdiag.AppendErrorf(diags, "deleting WAF Size Constraint Set (%s): %s", d.Id(), err) } return diags } -func updateSizeConstraintSetResource(ctx context.Context, id string, oldS, newS []interface{}, conn *waf.WAF) error { +func updateSizeConstraintSetResource(ctx context.Context, conn *waf.WAF, id string, oldS, newS []interface{}) error { + input := &waf.UpdateSizeConstraintSetInput{ + SizeConstraintSetId: aws.String(id), + Updates: DiffSizeConstraints(oldS, newS), + } + wr := NewRetryer(conn) _, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - req := &waf.UpdateSizeConstraintSetInput{ - ChangeToken: token, - SizeConstraintSetId: aws.String(id), - Updates: DiffSizeConstraints(oldS, newS), - } + input.ChangeToken = token - log.Printf("[INFO] Updating WAF Size Constraint constraints: %s", req) - return conn.UpdateSizeConstraintSetWithContext(ctx, req) + return conn.UpdateSizeConstraintSetWithContext(ctx, input) }) + + return err +} + +func FindSizeConstraintSetByID(ctx context.Context, conn *waf.WAF, id string) (*waf.SizeConstraintSet, error) { + input := &waf.GetSizeConstraintSetInput{ + SizeConstraintSetId: aws.String(id), + } + + output, err := conn.GetSizeConstraintSetWithContext(ctx, input) + + if tfawserr.ErrCodeEquals(err, waf.ErrCodeNonexistentItemException) { + return nil, &resource.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + if err != nil { - return fmt.Errorf("Error updating SizeConstraintSet: %s", err) + return nil, err + } + + if output == nil || output.SizeConstraintSet == nil { + return nil, tfresource.NewEmptyResultError(input) } - return nil + return output.SizeConstraintSet, nil } diff --git a/internal/service/waf/size_constraint_set_test.go b/internal/service/waf/size_constraint_set_test.go index 30b7e9325d4..12bce3c3d5a 100644 --- a/internal/service/waf/size_constraint_set_test.go +++ b/internal/service/waf/size_constraint_set_test.go @@ -6,15 +6,14 @@ import ( "regexp" "testing" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/waf" - "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" tfwaf "github.com/hashicorp/terraform-provider-aws/internal/service/waf" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) func TestAccWAFSizeConstraintSet_basic(t *testing.T) { @@ -111,7 +110,7 @@ func TestAccWAFSizeConstraintSet_disappears(t *testing.T) { Config: testAccSizeConstraintSetConfig_basic(sizeConstraintSet), Check: resource.ComposeTestCheckFunc( testAccCheckSizeConstraintSetExists(ctx, resourceName, &v), - testAccCheckSizeConstraintSetDisappears(ctx, &v), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfwaf.ResourceSizeConstraintSet(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -205,47 +204,6 @@ func TestAccWAFSizeConstraintSet_noConstraints(t *testing.T) { }) } -func testAccCheckSizeConstraintSetDisappears(ctx context.Context, v *waf.SizeConstraintSet) resource.TestCheckFunc { - return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).WAFConn() - - wr := tfwaf.NewRetryer(conn) - _, err := wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - req := &waf.UpdateSizeConstraintSetInput{ - ChangeToken: token, - SizeConstraintSetId: v.SizeConstraintSetId, - } - - for _, sizeConstraint := range v.SizeConstraints { - sizeConstraintUpdate := &waf.SizeConstraintSetUpdate{ - Action: aws.String("DELETE"), - SizeConstraint: &waf.SizeConstraint{ - FieldToMatch: sizeConstraint.FieldToMatch, - ComparisonOperator: sizeConstraint.ComparisonOperator, - Size: sizeConstraint.Size, - TextTransformation: sizeConstraint.TextTransformation, - }, - } - req.Updates = append(req.Updates, sizeConstraintUpdate) - } - return conn.UpdateSizeConstraintSetWithContext(ctx, req) - }) - if err != nil { - return fmt.Errorf("Error updating SizeConstraintSet: %s", err) - } - - _, err = wr.RetryWithToken(ctx, func(token *string) (interface{}, error) { - opts := &waf.DeleteSizeConstraintSetInput{ - ChangeToken: token, - SizeConstraintSetId: v.SizeConstraintSetId, - } - return conn.DeleteSizeConstraintSetWithContext(ctx, opts) - }) - - return err - } -} - func testAccCheckSizeConstraintSetExists(ctx context.Context, n string, v *waf.SizeConstraintSet) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -254,24 +212,20 @@ func testAccCheckSizeConstraintSetExists(ctx context.Context, n string, v *waf.S } if rs.Primary.ID == "" { - return fmt.Errorf("No WAF SizeConstraintSet ID is set") + return fmt.Errorf("No WAF Size Constraint Set ID is set") } conn := acctest.Provider.Meta().(*conns.AWSClient).WAFConn() - resp, err := conn.GetSizeConstraintSetWithContext(ctx, &waf.GetSizeConstraintSetInput{ - SizeConstraintSetId: aws.String(rs.Primary.ID), - }) + + output, err := tfwaf.FindSizeConstraintSetByID(ctx, conn, rs.Primary.ID) if err != nil { return err } - if *resp.SizeConstraintSet.SizeConstraintSetId == rs.Primary.ID { - *v = *resp.SizeConstraintSet - return nil - } + *v = *output - return fmt.Errorf("WAF SizeConstraintSet (%s) not found", rs.Primary.ID) + return nil } } @@ -283,21 +237,18 @@ func testAccCheckSizeConstraintSetDestroy(ctx context.Context) resource.TestChec } conn := acctest.Provider.Meta().(*conns.AWSClient).WAFConn() - resp, err := conn.GetSizeConstraintSetWithContext(ctx, &waf.GetSizeConstraintSetInput{ - SizeConstraintSetId: aws.String(rs.Primary.ID), - }) - - if err == nil { - if *resp.SizeConstraintSet.SizeConstraintSetId == rs.Primary.ID { - return fmt.Errorf("WAF SizeConstraintSet %s still exists", rs.Primary.ID) - } - } - if tfawserr.ErrCodeEquals(err, waf.ErrCodeNonexistentItemException) { + _, err := tfwaf.FindSizeConstraintSetByID(ctx, conn, rs.Primary.ID) + + if tfresource.NotFound(err) { continue } - return err + if err != nil { + return err + } + + return fmt.Errorf("WAF Size Constraint Set %s still exists", rs.Primary.ID) } return nil @@ -307,7 +258,7 @@ func testAccCheckSizeConstraintSetDestroy(ctx context.Context) resource.TestChec func testAccSizeConstraintSetConfig_basic(name string) string { return fmt.Sprintf(` resource "aws_waf_size_constraint_set" "size_constraint_set" { - name = "%s" + name = %[1]q size_constraints { text_transformation = "NONE" @@ -325,7 +276,7 @@ resource "aws_waf_size_constraint_set" "size_constraint_set" { func testAccSizeConstraintSetConfig_changeName(name string) string { return fmt.Sprintf(` resource "aws_waf_size_constraint_set" "size_constraint_set" { - name = "%s" + name = %[1]q size_constraints { text_transformation = "NONE" @@ -343,7 +294,7 @@ resource "aws_waf_size_constraint_set" "size_constraint_set" { func testAccSizeConstraintSetConfig_changes(name string) string { return fmt.Sprintf(` resource "aws_waf_size_constraint_set" "size_constraint_set" { - name = "%s" + name = %[1]q size_constraints { text_transformation = "NONE" @@ -361,7 +312,7 @@ resource "aws_waf_size_constraint_set" "size_constraint_set" { func testAccSizeConstraintSetConfig_nos(name string) string { return fmt.Sprintf(` resource "aws_waf_size_constraint_set" "size_constraint_set" { - name = "%s" + name = %[1]q } `, name) } diff --git a/website/docs/r/efs_mount_target.html.markdown b/website/docs/r/efs_mount_target.html.markdown index a049ba63a91..19fc8dfbae9 100644 --- a/website/docs/r/efs_mount_target.html.markdown +++ b/website/docs/r/efs_mount_target.html.markdown @@ -57,6 +57,13 @@ In addition to all arguments above, the following attributes are exported: * `availability_zone_id` - The unique and consistent identifier of the Availability Zone (AZ) that the mount target resides in. * `owner_id` - AWS account ID that owns the resource. +## Timeouts + +[Configuration options](https://developer.hashicorp.com/terraform/language/resources/syntax#operation-timeouts): + +- `create` - (Default `30m`) +- `delete` - (Default `10m`) + ## Import The EFS mount targets can be imported using the `id`, e.g.,