diff --git a/.changelog/15136.txt b/.changelog/15136.txt new file mode 100644 index 00000000000..9c54c206ec0 --- /dev/null +++ b/.changelog/15136.txt @@ -0,0 +1,19 @@ +```release-note:bug +resource/aws_config_configuration_recorder: Fix `panic: interface conversion: interface {} is nil, not map[string]interface {}` when `recording_group.exclusion_by_resource_types` is empty +``` + +```release-note:enhancement +resource/aws_config_configuration_recorder: Add plan-time validation of `aws_config_organization_custom_rule.lambda_function_arn` +``` + +```release-note:bug +resource/aws_config_rule: Change `name` to [ForceNew](https://developer.hashicorp.com/terraform/plugin/sdkv2/schemas/schema-behaviors#forcenew) +``` + +```release-note:new-resource +aws_config_retention_configuration +``` + +```release-note:bug +resource/aws_config_rule: Fix `InvalidParameterValueException: PolicyText is required when Owner is CUSTOM_POLICY` errors on resource Update +``` diff --git a/go.mod b/go.mod index 45f28cba46e..06eda05a812 100644 --- a/go.mod +++ b/go.mod @@ -49,6 +49,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/codestarnotifications v1.22.1 github.com/aws/aws-sdk-go-v2/service/comprehend v1.31.1 github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.33.1 + github.com/aws/aws-sdk-go-v2/service/configservice v1.46.1 github.com/aws/aws-sdk-go-v2/service/connectcases v1.15.1 github.com/aws/aws-sdk-go-v2/service/controltower v1.13.1 github.com/aws/aws-sdk-go-v2/service/costoptimizationhub v1.4.1 diff --git a/go.sum b/go.sum index a87757ae3a7..62c45190139 100644 --- a/go.sum +++ b/go.sum @@ -120,6 +120,8 @@ github.com/aws/aws-sdk-go-v2/service/comprehend v1.31.1 h1:CAoDG5wkvJ8x/woXDxsnS github.com/aws/aws-sdk-go-v2/service/comprehend v1.31.1/go.mod h1:tWhHJ9LUWQEdX5wwopa00xsTkYrOgi1sDwUxNuFYMfI= github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.33.1 h1:rQpdG0ooVj8GhQDJSpXnFJRiUCSfftT4O4AI0zqNsBA= github.com/aws/aws-sdk-go-v2/service/computeoptimizer v1.33.1/go.mod h1:3dkJ/DPuEmKrFcbqNuBbhmFWe/6D8I5sOwCO5OZFEXk= +github.com/aws/aws-sdk-go-v2/service/configservice v1.46.1 h1:emsv8VBtQkrBm2OJ4qT80e03KCtJj6zfKuWnDYqwtNE= +github.com/aws/aws-sdk-go-v2/service/configservice v1.46.1/go.mod h1:cuYIudpyQM/SiVg/P/UZBzThfq1IbXnswiyYDBFcTY4= github.com/aws/aws-sdk-go-v2/service/connectcases v1.15.1 h1:CQg3/P8gT7Kg2mYBM/V6oGyW88QrC2wYlPG9/mGJ5rc= github.com/aws/aws-sdk-go-v2/service/connectcases v1.15.1/go.mod h1:MyqG3o70o57M1M7Bw0X7hffxp8DhPKp4bgvovzwxNOA= github.com/aws/aws-sdk-go-v2/service/controltower v1.13.1 h1:+ADMUxi3W7Wq+f2XQPpZiKBAylHZkjk8NLwYXM3VerY= diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go index 59010853eab..3bd0761314c 100644 --- a/internal/acctest/acctest.go +++ b/internal/acctest/acctest.go @@ -2301,7 +2301,7 @@ resource "aws_vpc" "vpc_for_lambda" { assign_generated_ipv6_cidr_block = true tags = { - Name = "terraform-testacc-lambda-function" + Name = %[3]q } } @@ -2313,7 +2313,7 @@ resource "aws_subnet" "subnet_for_lambda" { assign_ipv6_address_on_creation = true tags = { - Name = "tf-acc-lambda-function-1" + Name = %[3]q } } @@ -2327,7 +2327,7 @@ resource "aws_subnet" "subnet_for_lambda_az2" { assign_ipv6_address_on_creation = true tags = { - Name = "tf-acc-lambda-function-2" + Name = %[3]q } } diff --git a/internal/acctest/vcr.go b/internal/acctest/vcr.go index 500390a5b2e..5fea432cbe6 100644 --- a/internal/acctest/vcr.go +++ b/internal/acctest/vcr.go @@ -78,7 +78,7 @@ var ( ) // ProviderMeta returns the current provider's state (AKA "meta" or "conns.AWSClient"). -func ProviderMeta(t *testing.T) *conns.AWSClient { +func ProviderMeta(_ context.Context, t *testing.T) *conns.AWSClient { t.Helper() providerMetas.Lock() @@ -108,14 +108,14 @@ func vcrMode() (recorder.Mode, error) { } // vcrEnabledProtoV5ProviderFactories returns ProtoV5ProviderFactories ready for use with VCR. -func vcrEnabledProtoV5ProviderFactories(t *testing.T, input map[string]func() (tfprotov5.ProviderServer, error)) map[string]func() (tfprotov5.ProviderServer, error) { +func vcrEnabledProtoV5ProviderFactories(ctx context.Context, t *testing.T, input map[string]func() (tfprotov5.ProviderServer, error)) map[string]func() (tfprotov5.ProviderServer, error) { t.Helper() output := make(map[string]func() (tfprotov5.ProviderServer, error), len(input)) for name := range input { output[name] = func() (tfprotov5.ProviderServer, error) { - providerServerFactory, primary, err := provider.ProtoV5ProviderServerFactory(context.Background()) + providerServerFactory, primary, err := provider.ProtoV5ProviderServerFactory(ctx) if err != nil { return nil, err @@ -383,7 +383,7 @@ func writeSeedToFile(seed int64, fileName string) error { } // closeVCRRecorder closes the VCR recorder, saving the cassette and randomness seed. -func closeVCRRecorder(t *testing.T) { +func closeVCRRecorder(ctx context.Context, t *testing.T) { t.Helper() // Don't close the recorder if we're running because of a panic. @@ -391,7 +391,6 @@ func closeVCRRecorder(t *testing.T) { panic(p) } - ctx := context.TODO() // nosemgrep:ci.semgrep.migrate.context-todo testName := t.Name() providerMetas.Lock() meta, ok := providerMetas[testName] @@ -428,24 +427,24 @@ func closeVCRRecorder(t *testing.T) { } // ParallelTest wraps resource.ParallelTest, initializing VCR if enabled. -func ParallelTest(t *testing.T, c resource.TestCase) { +func ParallelTest(ctx context.Context, t *testing.T, c resource.TestCase) { t.Helper() if isVCREnabled() { - c.ProtoV5ProviderFactories = vcrEnabledProtoV5ProviderFactories(t, c.ProtoV5ProviderFactories) - defer closeVCRRecorder(t) + c.ProtoV5ProviderFactories = vcrEnabledProtoV5ProviderFactories(ctx, t, c.ProtoV5ProviderFactories) + defer closeVCRRecorder(ctx, t) } resource.ParallelTest(t, c) } // Test wraps resource.Test, initializing VCR if enabled. -func Test(t *testing.T, c resource.TestCase) { +func Test(ctx context.Context, t *testing.T, c resource.TestCase) { t.Helper() if isVCREnabled() { - c.ProtoV5ProviderFactories = vcrEnabledProtoV5ProviderFactories(t, c.ProtoV5ProviderFactories) - defer closeVCRRecorder(t) + c.ProtoV5ProviderFactories = vcrEnabledProtoV5ProviderFactories(ctx, t, c.ProtoV5ProviderFactories) + defer closeVCRRecorder(ctx, t) } resource.Test(t, c) diff --git a/internal/acctest/vcr_test.go b/internal/acctest/vcr_test.go index 72e1aecb010..7ff8e595279 100644 --- a/internal/acctest/vcr_test.go +++ b/internal/acctest/vcr_test.go @@ -10,12 +10,14 @@ import ( ) func TestRandInt(t *testing.T) { + ctx := acctest.Context(t) + t.Setenv("VCR_PATH", t.TempDir()) t.Setenv("VCR_MODE", "RECORDING") rec1 := acctest.RandInt(t) rec2 := acctest.RandInt(t) - acctest.CloseVCRRecorder(t) + acctest.CloseVCRRecorder(ctx, t) t.Setenv("VCR_MODE", "REPLAYING") rep1 := acctest.RandInt(t) @@ -30,12 +32,14 @@ func TestRandInt(t *testing.T) { } func TestRandomWithPrefix(t *testing.T) { + ctx := acctest.Context(t) + t.Setenv("VCR_PATH", t.TempDir()) t.Setenv("VCR_MODE", "RECORDING") rec1 := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) rec2 := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) - acctest.CloseVCRRecorder(t) + acctest.CloseVCRRecorder(ctx, t) t.Setenv("VCR_MODE", "REPLAYING") rep1 := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) diff --git a/internal/conns/awsclient_gen.go b/internal/conns/awsclient_gen.go index f195b39a56b..ee560f6faa1 100644 --- a/internal/conns/awsclient_gen.go +++ b/internal/conns/awsclient_gen.go @@ -42,6 +42,7 @@ import ( codestarnotifications_sdkv2 "github.com/aws/aws-sdk-go-v2/service/codestarnotifications" comprehend_sdkv2 "github.com/aws/aws-sdk-go-v2/service/comprehend" computeoptimizer_sdkv2 "github.com/aws/aws-sdk-go-v2/service/computeoptimizer" + configservice_sdkv2 "github.com/aws/aws-sdk-go-v2/service/configservice" connectcases_sdkv2 "github.com/aws/aws-sdk-go-v2/service/connectcases" controltower_sdkv2 "github.com/aws/aws-sdk-go-v2/service/controltower" costoptimizationhub_sdkv2 "github.com/aws/aws-sdk-go-v2/service/costoptimizationhub" @@ -157,7 +158,6 @@ import ( cloudwatchrum_sdkv1 "github.com/aws/aws-sdk-go/service/cloudwatchrum" cognitoidentity_sdkv1 "github.com/aws/aws-sdk-go/service/cognitoidentity" cognitoidentityprovider_sdkv1 "github.com/aws/aws-sdk-go/service/cognitoidentityprovider" - configservice_sdkv1 "github.com/aws/aws-sdk-go/service/configservice" connect_sdkv1 "github.com/aws/aws-sdk-go/service/connect" costandusagereportservice_sdkv1 "github.com/aws/aws-sdk-go/service/costandusagereportservice" costexplorer_sdkv1 "github.com/aws/aws-sdk-go/service/costexplorer" @@ -483,8 +483,8 @@ func (c *AWSClient) ComputeOptimizerClient(ctx context.Context) *computeoptimize return errs.Must(client[*computeoptimizer_sdkv2.Client](ctx, c, names.ComputeOptimizer, make(map[string]any))) } -func (c *AWSClient) ConfigServiceConn(ctx context.Context) *configservice_sdkv1.ConfigService { - return errs.Must(conn[*configservice_sdkv1.ConfigService](ctx, c, names.ConfigService, make(map[string]any))) +func (c *AWSClient) ConfigServiceClient(ctx context.Context) *configservice_sdkv2.Client { + return errs.Must(client[*configservice_sdkv2.Client](ctx, c, names.ConfigService, make(map[string]any))) } func (c *AWSClient) ConnectConn(ctx context.Context) *connect_sdkv1.Connect { diff --git a/internal/service/cloudhsmv2/service_package.go b/internal/service/cloudhsmv2/service_package.go index d7e5f62dd52..386c4d14d9e 100644 --- a/internal/service/cloudhsmv2/service_package.go +++ b/internal/service/cloudhsmv2/service_package.go @@ -6,19 +6,28 @@ package cloudhsmv2 import ( "context" - aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - request_sdkv1 "github.com/aws/aws-sdk-go/aws/request" - cloudhsmv2_sdkv1 "github.com/aws/aws-sdk-go/service/cloudhsmv2" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/service/cloudhsmv2" + "github.com/aws/aws-sdk-go-v2/service/cloudhsmv2/types" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" ) -// CustomizeConn customizes a new AWS SDK for Go v1 client for this service package's AWS API. -func (p *servicePackage) CustomizeConn(ctx context.Context, conn *cloudhsmv2_sdkv1.CloudHSMV2) (*cloudhsmv2_sdkv1.CloudHSMV2, error) { - conn.Handlers.Retry.PushBack(func(r *request_sdkv1.Request) { - if tfawserr.ErrMessageContains(r.Error, cloudhsmv2_sdkv1.ErrCodeCloudHsmInternalFailureException, "request was rejected because of an AWS CloudHSM internal failure") { - r.Retryable = aws_sdkv1.Bool(true) +// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*cloudhsmv2.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws.Config)) + + return cloudhsmv2.NewFromConfig(cfg, func(o *cloudhsmv2.Options) { + if endpoint := config["endpoint"].(string); endpoint != "" { + o.BaseEndpoint = aws.String(endpoint) } - }) - return conn, nil + o.Retryer = conns.AddIsErrorRetryables(cfg.Retryer().(aws.RetryerV2), retry.IsErrorRetryableFunc(func(err error) aws.Ternary { + if errs.IsAErrorMessageContains[*types.CloudHsmInternalFailureException](err, "request was rejected because of an AWS CloudHSM internal failure") { + return aws.TrueTernary + } + return aws.UnknownTernary // Delegate to configured Retryer. + })) + }), nil } diff --git a/internal/service/cloudhsmv2/service_package_gen.go b/internal/service/cloudhsmv2/service_package_gen.go index 6d87c0ab583..20fdd099b54 100644 --- a/internal/service/cloudhsmv2/service_package_gen.go +++ b/internal/service/cloudhsmv2/service_package_gen.go @@ -5,8 +5,6 @@ package cloudhsmv2 import ( "context" - aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" - cloudhsmv2_sdkv2 "github.com/aws/aws-sdk-go-v2/service/cloudhsmv2" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/types" "github.com/hashicorp/terraform-provider-aws/names" @@ -54,17 +52,6 @@ func (p *servicePackage) ServicePackageName() string { return names.CloudHSMV2 } -// NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. -func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*cloudhsmv2_sdkv2.Client, error) { - cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) - - return cloudhsmv2_sdkv2.NewFromConfig(cfg, func(o *cloudhsmv2_sdkv2.Options) { - if endpoint := config["endpoint"].(string); endpoint != "" { - o.BaseEndpoint = aws_sdkv2.String(endpoint) - } - }), nil -} - func ServicePackage(ctx context.Context) conns.ServicePackage { return &servicePackage{} } diff --git a/internal/service/codestarnotifications/sweep.go b/internal/service/codestarnotifications/sweep.go index 1640843c9dd..322faf82e90 100644 --- a/internal/service/codestarnotifications/sweep.go +++ b/internal/service/codestarnotifications/sweep.go @@ -41,7 +41,7 @@ func sweepNotificationRules(region string) error { } if err != nil { - return fmt.Errorf("error listingCodeStar Notification Rules (%s): %w", region, err) + return fmt.Errorf("error listing CodeStar Notification Rules (%s): %w", region, err) } for _, v := range page.NotificationRules { diff --git a/internal/service/configservice/aggregate_authorization.go b/internal/service/configservice/aggregate_authorization.go index 33ba42ec214..18652bb8837 100644 --- a/internal/service/configservice/aggregate_authorization.go +++ b/internal/service/configservice/aggregate_authorization.go @@ -5,17 +5,18 @@ package configservice import ( "context" - "errors" "fmt" + "log" "strings" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "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" + tfslices "github.com/hashicorp/terraform-provider-aws/internal/slices" 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,9 +25,9 @@ import ( // @SDKResource("aws_config_aggregate_authorization", name="Aggregate Authorization") // @Tags(identifierAttribute="arn") -func ResourceAggregateAuthorization() *schema.Resource { +func resourceAggregateAuthorization() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceAggregateAuthorizationPut, + CreateWithoutTimeout: resourceAggregateAuthorizationCreate, ReadWithoutTimeout: resourceAggregateAuthorizationRead, UpdateWithoutTimeout: resourceAggregateAuthorizationUpdate, DeleteWithoutTimeout: resourceAggregateAuthorizationDelete, @@ -36,16 +37,16 @@ func ResourceAggregateAuthorization() *schema.Resource { }, Schema: map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Computed: true, - }, "account_id": { Type: schema.TypeString, Required: true, ForceNew: true, ValidateFunc: verify.ValidAccountID, }, + "arn": { + Type: schema.TypeString, + Computed: true, + }, "region": { Type: schema.TypeString, Required: true, @@ -59,70 +60,53 @@ func ResourceAggregateAuthorization() *schema.Resource { } } -func resourceAggregateAuthorizationPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceAggregateAuthorizationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - accountId := d.Get("account_id").(string) - region := d.Get("region").(string) + accountID, region := d.Get("account_id").(string), d.Get("region").(string) + id := aggregateAuthorizationCreateResourceID(accountID, region) input := &configservice.PutAggregationAuthorizationInput{ - AuthorizedAccountId: aws.String(accountId), + AuthorizedAccountId: aws.String(accountID), AuthorizedAwsRegion: aws.String(region), Tags: getTagsIn(ctx), } - _, err := conn.PutAggregationAuthorizationWithContext(ctx, input) + _, err := conn.PutAggregationAuthorization(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating aggregate authorization: %s", err) + return sdkdiag.AppendErrorf(diags, "putting ConfigService Aggregate Authorization (%s): %s", id, err) } - d.SetId(fmt.Sprintf("%s:%s", accountId, region)) + d.SetId(id) return append(diags, resourceAggregateAuthorizationRead(ctx, d, meta)...) } func resourceAggregateAuthorizationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - accountId, region, err := AggregateAuthorizationParseID(d.Id()) + accountID, region, err := aggregateAuthorizationParseResourceID(d.Id()) if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameAggregateAuthorization, d.Id(), err) + return sdkdiag.AppendFromErr(diags, err) } - d.Set("account_id", accountId) - d.Set("region", region) + aggregationAuthorization, err := findAggregateAuthorizationByTwoPartKey(ctx, conn, accountID, region) - aggregateAuthorizations, err := DescribeAggregateAuthorizations(ctx, conn) if !d.IsNewResource() && tfresource.NotFound(err) { - create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameAggregateAuthorization, d.Id()) + log.Printf("[WARN] ConfigService Aggregate Authorization (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameAggregateAuthorization, d.Id(), err) - } - - var aggregationAuthorization *configservice.AggregationAuthorization - // Check for existing authorization - for _, auth := range aggregateAuthorizations { - if accountId == aws.StringValue(auth.AuthorizedAccountId) && region == aws.StringValue(auth.AuthorizedAwsRegion) { - aggregationAuthorization = auth - } - } - - if !d.IsNewResource() && aggregationAuthorization == nil { - create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameAggregateAuthorization, d.Id()) - d.SetId("") - return diags - } - - if d.IsNewResource() && aggregationAuthorization == nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameAggregateAuthorization, d.Id(), errors.New("not found after creation")) + return sdkdiag.AppendErrorf(diags, "reading ConfigService Aggregate Authorization (%s): %s", d.Id(), err) } + d.Set("account_id", aggregationAuthorization.AuthorizedAccountId) d.Set("arn", aggregationAuthorization.AggregationAuthorizationArn) + d.Set("region", aggregationAuthorization.AuthorizedAwsRegion) return diags } @@ -137,54 +121,80 @@ func resourceAggregateAuthorizationUpdate(ctx context.Context, d *schema.Resourc func resourceAggregateAuthorizationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - accountId, region, err := AggregateAuthorizationParseID(d.Id()) + accountID, region, err := aggregateAuthorizationParseResourceID(d.Id()) if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Config Aggregate Authorization (%s): %s", d.Id(), err) + return sdkdiag.AppendFromErr(diags, err) } - req := &configservice.DeleteAggregationAuthorizationInput{ - AuthorizedAccountId: aws.String(accountId), + log.Printf("[DEBUG] Deleting ConfigService Aggregate Authorization: %s", d.Id()) + _, err = conn.DeleteAggregationAuthorization(ctx, &configservice.DeleteAggregationAuthorizationInput{ + AuthorizedAccountId: aws.String(accountID), AuthorizedAwsRegion: aws.String(region), - } + }) - _, err = conn.DeleteAggregationAuthorizationWithContext(ctx, req) if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Config Aggregate Authorization (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Aggregate Authorization (%s): %s", d.Id(), err) } return diags } -func DescribeAggregateAuthorizations(ctx context.Context, conn *configservice.ConfigService) ([]*configservice.AggregationAuthorization, error) { - aggregationAuthorizations := []*configservice.AggregationAuthorization{} +const aggregateAuthorizationResourceIDSeparator = ":" + +func aggregateAuthorizationCreateResourceID(accountID, region string) string { + parts := []string{accountID, region} + id := strings.Join(parts, aggregateAuthorizationResourceIDSeparator) + + return id +} + +func aggregateAuthorizationParseResourceID(id string) (string, string, error) { + parts := strings.Split(id, aggregateAuthorizationResourceIDSeparator) + + if len(parts) == 2 && parts[0] != "" && parts[1] != "" { + return parts[0], parts[1], nil + } + + return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected account_id%[2]sregion", id, aggregateAuthorizationResourceIDSeparator) +} + +func findAggregateAuthorizationByTwoPartKey(ctx context.Context, conn *configservice.Client, accountID, region string) (*types.AggregationAuthorization, error) { input := &configservice.DescribeAggregationAuthorizationsInput{} - for { - output, err := conn.DescribeAggregationAuthorizationsWithContext(ctx, input) - if err != nil { - return aggregationAuthorizations, err - } - aggregationAuthorizations = append(aggregationAuthorizations, output.AggregationAuthorizations...) - if output.NextToken == nil { - break - } - input.NextToken = output.NextToken + return findAggregateAuthorization(ctx, conn, input, func(v *types.AggregationAuthorization) bool { + return aws.ToString(v.AuthorizedAccountId) == accountID && aws.ToString(v.AuthorizedAwsRegion) == region + }) +} + +func findAggregateAuthorization(ctx context.Context, conn *configservice.Client, input *configservice.DescribeAggregationAuthorizationsInput, filter tfslices.Predicate[*types.AggregationAuthorization]) (*types.AggregationAuthorization, error) { + output, err := findAggregateAuthorizations(ctx, conn, input, filter) + + if err != nil { + return nil, err } - return aggregationAuthorizations, nil + return tfresource.AssertSingleValueResult(output) } -func AggregateAuthorizationParseID(id string) (string, string, error) { - const ( - resourceIDSeparator = ":" - ) - parts := strings.Split(id, resourceIDSeparator) +func findAggregateAuthorizations(ctx context.Context, conn *configservice.Client, input *configservice.DescribeAggregationAuthorizationsInput, filter tfslices.Predicate[*types.AggregationAuthorization]) ([]types.AggregationAuthorization, error) { + var output []types.AggregationAuthorization - if len(parts) != 2 { - return "", "", fmt.Errorf("unexpected format for ID (%[1]s), expected account_id%[2]sregion", id, resourceIDSeparator) + pages := configservice.NewDescribeAggregationAuthorizationsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if err != nil { + return nil, err + } + + for _, v := range page.AggregationAuthorizations { + if filter(&v) { + output = append(output, v) + } + } } - return parts[0], parts[1], nil + return output, nil } diff --git a/internal/service/configservice/aggregate_authorization_test.go b/internal/service/configservice/aggregate_authorization_test.go index 743842f563b..243982462db 100644 --- a/internal/service/configservice/aggregate_authorization_test.go +++ b/internal/service/configservice/aggregate_authorization_test.go @@ -3,131 +3,201 @@ package configservice_test -// func TestAccConfigServiceAggregateAuthorization_basic(t *testing.T) { -// rString := sdkacctest.RandStringFromCharSet(12, "0123456789") -// resourceName := "aws_config_aggregate_authorization.example" -// dataSourceName := "data.aws_region.current" - -// resource.ParallelTest(t, resource.TestCase{ -// PreCheck: func() { acctest.PreCheck(ctx, t) }, -// ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), -// ProtoV5ProviderFactories:acctest.ProtoV5ProviderFactories, -// CheckDestroy: testAccCheckAggregateAuthorizationDestroy, -// Steps: []resource.TestStep{ -// { -// Config: testAccAggregateAuthorizationConfig_basic(rString), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "account_id", rString), -// resource.TestCheckResourceAttrPair(resourceName, "region", dataSourceName, "name"), -// acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "config", regexache.MustCompile(fmt.Sprintf(`aggregation-authorization/%s/%s$`, rString, acctest.Region()))), -// ), -// }, -// { -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// }, -// }, -// }) -// } - -// func TestAccConfigServiceAggregateAuthorization_tags(t *testing.T) { -// rString := sdkacctest.RandStringFromCharSet(12, "0123456789") -// resourceName := "aws_config_aggregate_authorization.example" - -// resource.ParallelTest(t, resource.TestCase{ -// PreCheck: func() { acctest.PreCheck(ctx, t) }, -// ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), -// ProtoV5ProviderFactories:acctest.ProtoV5ProviderFactories, -// CheckDestroy: testAccCheckAggregateAuthorizationDestroy, -// Steps: []resource.TestStep{ -// { -// Config: testAccAggregateAuthorizationConfig_tags(rString, "foo", "bar", "fizz", "buzz"), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), -// resource.TestCheckResourceAttr(resourceName, "tags.Name", rString), -// resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"), -// resource.TestCheckResourceAttr(resourceName, "tags.fizz", "buzz"), -// ), -// }, -// { -// Config: testAccAggregateAuthorizationConfig_tags(rString, "foo", "bar2", "fizz2", "buzz2"), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), -// resource.TestCheckResourceAttr(resourceName, "tags.Name", rString), -// resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar2"), -// resource.TestCheckResourceAttr(resourceName, "tags.fizz2", "buzz2"), -// ), -// }, -// { -// ResourceName: resourceName, -// ImportState: true, -// ImportStateVerify: true, -// }, -// { -// Config: testAccAggregateAuthorizationConfig_basic(rString), -// Check: resource.ComposeTestCheckFunc( -// resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), -// ), -// }, -// }, -// }) -// } - -// func testAccCheckAggregateAuthorizationDestroy(s *terraform.State) error { -// conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) - -// for _, rs := range s.RootModule().Resources { -// if rs.Type != "aws_config_aggregate_authorization" { -// continue -// } - -// accountId, region, err := tfconfig.AggregateAuthorizationParseID(rs.Primary.ID) -// if err != nil { -// return err -// } - -// aggregateAuthorizations, err := tfconfig.DescribeAggregateAuthorizations(conn) - -// if err != nil { -// return err -// } - -// for _, auth := range aggregateAuthorizations { -// if accountId == aws.StringValue(auth.AuthorizedAccountId) && region == aws.StringValue(auth.AuthorizedAwsRegion) { -// return fmt.Errorf("Config aggregate authorization still exists: %s", rs.Primary.ID) -// } -// } -// } - -// return nil -// } - -// func testAccAggregateAuthorizationConfig_basic(rString string) string { -// return fmt.Sprintf(` -// data "aws_region" "current" {} - -// resource "aws_config_aggregate_authorization" "example" { -// account_id = %[1]q -// region = data.aws_region.current.name -// } -// `, rString) -// } - -// func testAccAggregateAuthorizationConfig_tags(rString, tagKey1, tagValue1, tagKey2, tagValue2 string) string { -// return fmt.Sprintf(` -// data "aws_region" "current" {} - -// resource "aws_config_aggregate_authorization" "example" { -// account_id = %[1]q -// region = data.aws_region.current.name - -// tags = { -// Name = %[1]q - -// %[2]s = %[3]q -// %[4]s = %[5]q -// } -// } -// `, rString, tagKey1, tagValue1, tagKey2, tagValue2) -// } +import ( + "context" + "fmt" + "testing" + + "github.com/aws/aws-sdk-go-v2/service/configservice/types" + sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" +) + +func TestAccConfigServiceAggregateAuthorization_basic(t *testing.T) { + ctx := acctest.Context(t) + var aa types.AggregationAuthorization + accountID := sdkacctest.RandStringFromCharSet(12, "0123456789") + resourceName := "aws_config_aggregate_authorization.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAggregateAuthorizationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAggregateAuthorizationConfig_basic(accountID), + Check: resource.ComposeTestCheckFunc( + testAccCheckAggregateAuthorizationExists(ctx, resourceName, &aa), + resource.TestCheckResourceAttrSet(resourceName, "arn"), + resource.TestCheckResourceAttr(resourceName, "account_id", accountID), + resource.TestCheckResourceAttr(resourceName, "region", acctest.Region()), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccConfigServiceAggregateAuthorization_disappears(t *testing.T) { + ctx := acctest.Context(t) + var aa types.AggregationAuthorization + accountID := sdkacctest.RandStringFromCharSet(12, "0123456789") + resourceName := "aws_config_aggregate_authorization.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAggregateAuthorizationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAggregateAuthorizationConfig_basic(accountID), + Check: resource.ComposeTestCheckFunc( + testAccCheckAggregateAuthorizationExists(ctx, resourceName, &aa), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfconfig.ResourceAggregateAuthorization(), resourceName), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) +} + +func TestAccConfigServiceAggregateAuthorization_tags(t *testing.T) { + ctx := acctest.Context(t) + var aa types.AggregationAuthorization + accountID := sdkacctest.RandStringFromCharSet(12, "0123456789") + resourceName := "aws_config_aggregate_authorization.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckAggregateAuthorizationDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccAggregateAuthorizationConfig_tags1(accountID, "key1", "value1"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAggregateAuthorizationExists(ctx, resourceName, &aa), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccAggregateAuthorizationConfig_tags2(accountID, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAggregateAuthorizationExists(ctx, resourceName, &aa), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccAggregateAuthorizationConfig_tags1(accountID, "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAggregateAuthorizationExists(ctx, resourceName, &aa), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + }, + }) +} + +func testAccCheckAggregateAuthorizationExists(ctx context.Context, n string, v *types.AggregationAuthorization) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) + + output, err := tfconfig.FindAggregateAuthorizationByTwoPartKey(ctx, conn, rs.Primary.Attributes["account_id"], rs.Primary.Attributes["region"]) + + if err != nil { + return err + } + + *v = *output + + return nil + } +} + +func testAccCheckAggregateAuthorizationDestroy(ctx context.Context) resource.TestCheckFunc { + return func(s *terraform.State) error { + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_config_aggregate_authorization" { + continue + } + + _, err := tfconfig.FindAggregateAuthorizationByTwoPartKey(ctx, conn, rs.Primary.Attributes["account_id"], rs.Primary.Attributes["region"]) + + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err + } + + return fmt.Errorf("ConfigService Aggregate Authorization %s still exists", rs.Primary.ID) + } + + return nil + } +} + +func testAccAggregateAuthorizationConfig_basic(accountID string) string { + return fmt.Sprintf(` +resource "aws_config_aggregate_authorization" "test" { + account_id = %[1]q + region = %[2]q +} +`, accountID, acctest.Region()) +} + +func testAccAggregateAuthorizationConfig_tags1(accountID, tagKey1, tagValue1 string) string { + return fmt.Sprintf(` +resource "aws_config_aggregate_authorization" "test" { + account_id = %[1]q + region = %[2]q + + tags = { + %[3]q = %[4]q + } +} +`, accountID, acctest.Region(), tagKey1, tagValue1) +} + +func testAccAggregateAuthorizationConfig_tags2(accountID, tagKey1, tagValue1, tagKey2, tagValue2 string) string { + return fmt.Sprintf(` +resource "aws_config_aggregate_authorization" "test" { + account_id = %[1]q + region = %[2]q + + tags = { + %[3]q = %[4]q + %[5]q = %[6]q + } +} +`, accountID, acctest.Region(), tagKey1, tagValue1, tagKey2, tagValue2) +} diff --git a/internal/service/configservice/config_rule.go b/internal/service/configservice/config_rule.go index 9fa6abdcf87..d16c1528063 100644 --- a/internal/service/configservice/config_rule.go +++ b/internal/service/configservice/config_rule.go @@ -5,20 +5,22 @@ package configservice import ( "context" - "fmt" "log" "time" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" + "github.com/hashicorp/terraform-provider-aws/internal/flex" "github.com/hashicorp/terraform-provider-aws/internal/sdkv2" tftags "github.com/hashicorp/terraform-provider-aws/internal/tags" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" @@ -28,11 +30,11 @@ import ( // @SDKResource("aws_config_config_rule", name="Config Rule") // @Tags(identifierAttribute="arn") -func ResourceConfigRule() *schema.Resource { +func resourceConfigRule() *schema.Resource { return &schema.Resource{ - CreateWithoutTimeout: resourceRulePutConfig, + CreateWithoutTimeout: resourceConfigRulePut, ReadWithoutTimeout: resourceConfigRuleRead, - UpdateWithoutTimeout: resourceRulePutConfig, + UpdateWithoutTimeout: resourceConfigRulePut, DeleteWithoutTimeout: resourceConfigRuleDelete, Importer: &schema.ResourceImporter{ @@ -56,28 +58,29 @@ func ResourceConfigRule() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "mode": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ValidateFunc: validation.StringInSlice(configservice.EvaluationMode_Values(), false), + Type: schema.TypeString, + Optional: true, + Computed: true, + ValidateDiagFunc: enum.Validate[types.EvaluationMode](), }, }, }, }, - "name": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringLenBetween(0, 128), - }, "input_parameters": { Type: schema.TypeString, Optional: true, ValidateFunc: validation.StringIsJSON, }, "maximum_execution_frequency": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.MaximumExecutionFrequency](), + }, + "name": { Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(configservice.MaximumExecutionFrequency_Values(), false), + Required: true, + ForceNew: true, + ValidateFunc: validation.StringLenBetween(0, 128), }, "rule_id": { Type: schema.TypeString, @@ -146,20 +149,14 @@ func ResourceConfigRule() *schema.Resource { Type: schema.TypeString, Required: true, ValidateFunc: validation.StringLenBetween(0, 10000), - DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { // policy_text always returns empty - if d.Id() != "" && old == "" { - return true - } - return false - }, }, }, }, }, "owner": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(configservice.Owner_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.Owner](), }, "source_detail": { Type: schema.TypeSet, @@ -168,20 +165,20 @@ func ResourceConfigRule() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "event_source": { - Type: schema.TypeString, - Optional: true, - Default: "aws.config", - ValidateFunc: validation.StringInSlice(configservice.EventSource_Values(), false), + Type: schema.TypeString, + Optional: true, + Default: types.EventSourceAwsConfig, + ValidateDiagFunc: enum.Validate[types.EventSource](), }, "maximum_execution_frequency": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(configservice.MaximumExecutionFrequency_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.MaximumExecutionFrequency](), }, "message_type": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(configservice.MessageType_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.MessageType](), }, }, }, @@ -203,131 +200,433 @@ func ResourceConfigRule() *schema.Resource { } } -const ( - ResNameConfigRule = "Config Rule" -) - -func resourceRulePutConfig(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { +func resourceConfigRulePut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - name := d.Get("name").(string) - ruleInput := configservice.ConfigRule{ - ConfigRuleName: aws.String(name), - Scope: expandRuleScope(d.Get("scope").([]interface{})), - Source: expandRuleSource(d.Get("source").([]interface{})), - } + if d.IsNewResource() || d.HasChangesExcept("tags", "tags_all") { + name := d.Get("name").(string) + configRule := &types.ConfigRule{ + ConfigRuleName: aws.String(name), + } - if v, ok := d.GetOk("description"); ok { - ruleInput.Description = aws.String(v.(string)) - } + if v, ok := d.GetOk("description"); ok { + configRule.Description = aws.String(v.(string)) + } - if v, ok := d.Get("evaluation_mode").(*schema.Set); ok && v.Len() > 0 { - ruleInput.EvaluationModes = expandRulesEvaluationModes(v.List()) - } + if v, ok := d.Get("evaluation_mode").(*schema.Set); ok && v.Len() > 0 { + configRule.EvaluationModes = expandEvaluationModeConfigurations(v.List()) + } - if v, ok := d.GetOk("input_parameters"); ok { - ruleInput.InputParameters = aws.String(v.(string)) - } + if v, ok := d.GetOk("input_parameters"); ok { + configRule.InputParameters = aws.String(v.(string)) + } - if v, ok := d.GetOk("maximum_execution_frequency"); ok { - ruleInput.MaximumExecutionFrequency = aws.String(v.(string)) - } + if v, ok := d.GetOk("maximum_execution_frequency"); ok { + configRule.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v.(string)) + } - input := configservice.PutConfigRuleInput{ - ConfigRule: &ruleInput, - Tags: getTagsIn(ctx), - } - log.Printf("[DEBUG] Creating AWSConfig config rule: %s", input) - err := retry.RetryContext(ctx, propagationTimeout, func() *retry.RetryError { - _, err := conn.PutConfigRuleWithContext(ctx, &input) - if err != nil { - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeInsufficientPermissionsException) { - // IAM is eventually consistent - return retry.RetryableError(err) - } + if v, ok := d.GetOk("scope"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + configRule.Scope = expandScope(v.([]interface{})[0].(map[string]interface{})) + } - return retry.NonRetryableError(fmt.Errorf("Failed to create AWSConfig rule: %w", err)) + if v, ok := d.GetOk("source"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + configRule.Source = expandSource(v.([]interface{})[0].(map[string]interface{})) } - return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.PutConfigRuleWithContext(ctx, &input) - } - if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionUpdating, ResNameConfigRule, name, err) - } + input := &configservice.PutConfigRuleInput{ + ConfigRule: configRule, + Tags: getTagsIn(ctx), + } - d.SetId(name) + _, err := tfresource.RetryWhenIsA[*types.InsufficientPermissionsException](ctx, propagationTimeout, func() (interface{}, error) { + return conn.PutConfigRule(ctx, input) + }) + + if err != nil { + return sdkdiag.AppendErrorf(diags, "putting ConfigService Config Rule (%s): %s", name, err) + } + + if d.IsNewResource() { + d.SetId(name) + } + } return append(diags, resourceConfigRuleRead(ctx, d, meta)...) } func resourceConfigRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - rule, err := FindConfigRule(ctx, conn, d.Id()) + rule, err := findConfigRuleByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ConfigService Config Rule (%s) not found, removing from state", d.Id()) d.SetId("") return diags } + if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameConfigRule, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading ConfigService Config Rule (%s): %s", d.Id(), err) } - arn := aws.StringValue(rule.ConfigRuleArn) - d.Set("arn", arn) - d.Set("rule_id", rule.ConfigRuleId) - d.Set("name", rule.ConfigRuleName) + d.Set("arn", rule.ConfigRuleArn) d.Set("description", rule.Description) + if err := d.Set("evaluation_mode", flattenEvaluationModeConfigurations(rule.EvaluationModes)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting evaluation_mode: %s", err) + } d.Set("input_parameters", rule.InputParameters) d.Set("maximum_execution_frequency", rule.MaximumExecutionFrequency) - - d.Set("evaluation_mode", flattenRuleEvaluationMode(rule.EvaluationModes)) - + d.Set("name", rule.ConfigRuleName) + d.Set("rule_id", rule.ConfigRuleId) if rule.Scope != nil { - d.Set("scope", flattenRuleScope(rule.Scope)) + if err := d.Set("scope", flattenScope(rule.Scope)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting scope: %s", err) + } + } + if rule.Source != nil && rule.Source.CustomPolicyDetails != nil && aws.ToString(rule.Source.CustomPolicyDetails.PolicyText) == "" { + // Source.CustomPolicyDetails.PolicyText is not returned by the API, so copy from state. + if v, ok := d.GetOk("source.0.custom_policy_details.0.policy_text"); ok { + rule.Source.CustomPolicyDetails.PolicyText = aws.String(v.(string)) + } + } + if err := d.Set("source", flattenSource(rule.Source)); err != nil { + return sdkdiag.AppendErrorf(diags, "setting source: %s", err) } - - d.Set("source", flattenRuleSource(rule.Source)) return diags } func resourceConfigRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + const ( + timeout = 2 * time.Minute + ) + log.Printf("[DEBUG] Deleting ConfigService Config Rule: %s", d.Id()) + _, err := tfresource.RetryWhenIsA[*types.ResourceInUseException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteConfigRule(ctx, &configservice.DeleteConfigRuleInput{ + ConfigRuleName: aws.String(d.Id()), + }) + }) + + if errs.IsA[*types.NoSuchConfigRuleException](err) { + return diags + } - name := d.Get("name").(string) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Config Rule (%s): %s", d.Id(), err) + } - log.Printf("[DEBUG] Deleting AWS Config config rule %q", name) - input := &configservice.DeleteConfigRuleInput{ - ConfigRuleName: aws.String(name), + if _, err := waitConfigRuleDeleted(ctx, conn, d.Id()); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Config Rule (%s) delete: %s", d.Id(), err) } - err := retry.RetryContext(ctx, 2*time.Minute, func() *retry.RetryError { - _, err := conn.DeleteConfigRuleWithContext(ctx, input) - if err != nil { - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeResourceInUseException) { - return retry.RetryableError(err) + + return diags +} + +func findConfigRuleByName(ctx context.Context, conn *configservice.Client, name string) (*types.ConfigRule, error) { + input := &configservice.DescribeConfigRulesInput{ + ConfigRuleNames: []string{name}, + } + + return findConfigRule(ctx, conn, input) +} + +func findConfigRule(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigRulesInput) (*types.ConfigRule, error) { + output, err := findConfigRules(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findConfigRules(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigRulesInput) ([]types.ConfigRule, error) { + var output []types.ConfigRule + + pages := configservice.NewDescribeConfigRulesPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchConfigRuleException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } - return retry.NonRetryableError(err) } + + if err != nil { + return nil, err + } + + output = append(output, page.ConfigRules...) + } + + return output, nil +} + +func statusConfigRule(ctx context.Context, conn *configservice.Client, name string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findConfigRuleByName(ctx, conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.ConfigRuleState), nil + } +} + +func waitConfigRuleDeleted(ctx context.Context, conn *configservice.Client, name string) (*types.ConfigRule, error) { + const ( + timeout = 5 * time.Minute + ) + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice( + types.ConfigRuleStateActive, + types.ConfigRuleStateDeleting, + types.ConfigRuleStateDeletingResults, + types.ConfigRuleStateEvaluating, + ), + Target: []string{}, + Refresh: statusConfigRule(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if v, ok := outputRaw.(*types.ConfigRule); ok { + return v, err + } + + return nil, err +} + +func expandEvaluationModeConfigurations(tfList []interface{}) []types.EvaluationModeConfiguration { + if len(tfList) == 0 { return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.DeleteConfigRuleWithContext(ctx, input) } - if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionDeleting, ResNameConfigRule, d.Id(), err) + + var apiObjects []types.EvaluationModeConfiguration + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + + apiObject := types.EvaluationModeConfiguration{} + + if v, ok := tfMap["mode"].(string); ok && v != "" { + apiObject.Mode = types.EvaluationMode(v) + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandScope(tfMap map[string]interface{}) *types.Scope { + if tfMap == nil { + return nil } - if _, err := waitRuleDeleted(ctx, conn, d.Id()); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionWaitingForDeletion, ResNameConfigRule, d.Id(), err) + apiObject := &types.Scope{} + + if v, ok := tfMap["compliance_resource_id"].(string); ok && v != "" { + apiObject.ComplianceResourceId = aws.String(v) } - return diags + if v, ok := tfMap["compliance_resource_types"].(*schema.Set); ok && v.Len() > 0 { + apiObject.ComplianceResourceTypes = flex.ExpandStringValueSet(v) + } + + if v, ok := tfMap["tag_key"].(string); ok && v != "" { + apiObject.TagKey = aws.String(v) + } + + if v, ok := tfMap["tag_value"].(string); ok && v != "" { + apiObject.TagValue = aws.String(v) + } + + return apiObject +} + +func expandSource(tfMap map[string]interface{}) *types.Source { + if tfMap == nil { + return nil + } + + apiObject := &types.Source{ + Owner: types.Owner(tfMap["owner"].(string)), + } + + if v, ok := tfMap["custom_policy_details"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + apiObject.CustomPolicyDetails = expandCustomPolicyDetails(v[0].(map[string]interface{})) + } + + if v, ok := tfMap["source_detail"].(*schema.Set); ok && v.Len() > 0 { + apiObject.SourceDetails = expandSourceDetails(v.List()) + } + + if v, ok := tfMap["source_identifier"].(string); ok && v != "" { + apiObject.SourceIdentifier = aws.String(v) + } + + return apiObject +} + +func expandCustomPolicyDetails(tfMap map[string]interface{}) *types.CustomPolicyDetails { + if tfMap == nil { + return nil + } + + apiObject := &types.CustomPolicyDetails{ + EnableDebugLogDelivery: tfMap["enable_debug_log_delivery"].(bool), + PolicyRuntime: aws.String(tfMap["policy_runtime"].(string)), + PolicyText: aws.String(tfMap["policy_text"].(string)), + } + + return apiObject +} + +func expandSourceDetails(tfList []interface{}) []types.SourceDetail { + if len(tfList) == 0 { + return nil + } + + var apiObjects []types.SourceDetail + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + + apiObject := types.SourceDetail{} + + if v, ok := tfMap["event_source"].(string); ok && v != "" { + apiObject.EventSource = types.EventSource(v) + } + + if v, ok := tfMap["maximum_execution_frequency"].(string); ok && v != "" { + apiObject.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v) + } + + if v, ok := tfMap["message_type"].(string); ok && v != "" { + apiObject.MessageType = types.MessageType(v) + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenEvaluationModeConfigurations(apiObjects []types.EvaluationModeConfiguration) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "mode": apiObject.Mode, + } + + tfList = append(tfList, tfMap) + } + + return tfList +} + +func flattenScope(apiObject *types.Scope) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if apiObject.ComplianceResourceId != nil { + tfMap["compliance_resource_id"] = aws.ToString(apiObject.ComplianceResourceId) + } + + if apiObject.ComplianceResourceTypes != nil { + tfMap["compliance_resource_types"] = apiObject.ComplianceResourceTypes + } + + if apiObject.TagKey != nil { + tfMap["tag_key"] = aws.ToString(apiObject.TagKey) + } + + if apiObject.TagValue != nil { + tfMap["tag_value"] = aws.ToString(apiObject.TagValue) + } + + return []interface{}{tfMap} +} + +func flattenSource(apiObject *types.Source) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "owner": apiObject.Owner, + "source_identifier": aws.ToString(apiObject.SourceIdentifier), + } + + if apiObject.CustomPolicyDetails != nil { + tfMap["custom_policy_details"] = flattenCustomPolicyDetails(apiObject.CustomPolicyDetails) + } + + if len(apiObject.SourceDetails) > 0 { + tfMap["source_detail"] = flattenSourceDetails(apiObject.SourceDetails) + } + + return []interface{}{tfMap} +} + +func flattenCustomPolicyDetails(apiObject *types.CustomPolicyDetails) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "enable_debug_log_delivery": apiObject.EnableDebugLogDelivery, + "policy_runtime": aws.ToString(apiObject.PolicyRuntime), + "policy_text": aws.ToString(apiObject.PolicyText), + } + + return []interface{}{tfMap} +} + +func flattenSourceDetails(apiObjects []types.SourceDetail) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "event_source": apiObject.EventSource, + "maximum_execution_frequency": apiObject.MaximumExecutionFrequency, + "message_type": apiObject.MessageType, + } + + tfList = append(tfList, tfMap) + } + + return tfList } diff --git a/internal/service/configservice/config_rule_test.go b/internal/service/configservice/config_rule_test.go index 5a2f7f47e5d..4ae147ac2fb 100644 --- a/internal/service/configservice/config_rule_test.go +++ b/internal/service/configservice/config_rule_test.go @@ -9,7 +9,7 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -22,7 +22,7 @@ import ( func testAccConfigRule_basic(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigRule + var cr types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -34,9 +34,8 @@ func testAccConfigRule_basic(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccConfigRuleConfig_basic(rName), - Check: resource.ComposeTestCheckFunc( + Check: resource.ComposeAggregateTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - testAccCheckConfigRuleName(resourceName, rName, &cr), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "source.#", "1"), resource.TestCheckResourceAttr(resourceName, "source.0.owner", "AWS"), @@ -52,7 +51,7 @@ func testAccConfigRule_basic(t *testing.T) { func testAccConfigRule_evaluationMode(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigRule + var cr types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -81,7 +80,6 @@ func testAccConfigRule_evaluationMode(t *testing.T) { Config: testAccConfigRuleConfig_evaluationMode(rName, evaluationMode1), Check: resource.ComposeTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - testAccCheckConfigRuleName(resourceName, rName, &cr), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "evaluation_mode.*", map[string]string{ "mode": "DETECTIVE", @@ -92,7 +90,6 @@ func testAccConfigRule_evaluationMode(t *testing.T) { Config: testAccConfigRuleConfig_evaluationMode(rName, evaluationMode2), Check: resource.ComposeTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - testAccCheckConfigRuleName(resourceName, rName, &cr), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "evaluation_mode.*", map[string]string{ "mode": "DETECTIVE", @@ -106,9 +103,9 @@ func testAccConfigRule_evaluationMode(t *testing.T) { }) } -func testAccConfigRule_ownerAws(t *testing.T) { // nosemgrep:ci.aws-in-func-name +func testAccConfigRule_ownerAWS(t *testing.T) { // nosemgrep:ci.aws-in-func-name ctx := acctest.Context(t) - var cr configservice.ConfigRule + var cr types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -119,10 +116,9 @@ func testAccConfigRule_ownerAws(t *testing.T) { // nosemgrep:ci.aws-in-func-name CheckDestroy: testAccCheckConfigRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigRuleConfig_ownerAws(rName), + Config: testAccConfigRuleConfig_ownerAWS(rName), Check: resource.ComposeTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - testAccCheckConfigRuleName(resourceName, rName, &cr), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "config", regexache.MustCompile("config-rule/config-rule-[0-9a-z]+$")), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestMatchResourceAttr(resourceName, "rule_id", regexache.MustCompile("config-rule-[0-9a-z]+$")), @@ -148,13 +144,10 @@ func testAccConfigRule_ownerAws(t *testing.T) { // nosemgrep:ci.aws-in-func-name func testAccConfigRule_customlambda(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigRule - rInt := sdkacctest.RandInt() + var cr types.ConfigRule + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" - expectedName := fmt.Sprintf("tf-acc-test-%d", rInt) - path := "test-fixtures/lambdatest.zip" - resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), @@ -162,18 +155,17 @@ func testAccConfigRule_customlambda(t *testing.T) { CheckDestroy: testAccCheckConfigRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigRuleConfig_customLambda(rInt, path), + Config: testAccConfigRuleConfig_customLambda(rName), Check: resource.ComposeTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - testAccCheckConfigRuleName(resourceName, expectedName, &cr), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "config", regexache.MustCompile("config-rule/config-rule-[0-9a-z]+$")), - resource.TestCheckResourceAttr(resourceName, "name", expectedName), + resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestMatchResourceAttr(resourceName, "rule_id", regexache.MustCompile("config-rule-[0-9a-z]+$")), resource.TestCheckResourceAttr(resourceName, "description", "Terraform Acceptance tests"), resource.TestCheckResourceAttr(resourceName, "maximum_execution_frequency", "Six_Hours"), resource.TestCheckResourceAttr(resourceName, "source.#", "1"), resource.TestCheckResourceAttr(resourceName, "source.0.owner", "CUSTOM_LAMBDA"), - resource.TestCheckResourceAttrPair(resourceName, "source.0.source_identifier", "aws_lambda_function.f", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "source.0.source_identifier", "aws_lambda_function.test", "arn"), resource.TestCheckResourceAttr(resourceName, "source.0.source_detail.#", "1"), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "source.0.source_detail.*", map[string]string{ "event_source": "aws.config", @@ -196,7 +188,7 @@ func testAccConfigRule_customlambda(t *testing.T) { func testAccConfigRule_ownerPolicy(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigRule + var cr types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -207,27 +199,44 @@ func testAccConfigRule_ownerPolicy(t *testing.T) { CheckDestroy: testAccCheckConfigRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigRuleConfig_ownerPolicy(rName), + Config: testAccConfigRuleConfig_ownerPolicy(rName, false), Check: resource.ComposeTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - testAccCheckConfigRuleName(resourceName, rName, &cr), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "config", regexache.MustCompile("config-rule/config-rule-[0-9a-z]+$")), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestMatchResourceAttr(resourceName, "rule_id", regexache.MustCompile("config-rule-[0-9a-z]+$")), + resource.TestCheckResourceAttr(resourceName, "scope.#", "0"), resource.TestCheckResourceAttr(resourceName, "source.#", "1"), resource.TestCheckResourceAttr(resourceName, "source.0.owner", "CUSTOM_POLICY"), resource.TestCheckResourceAttr(resourceName, "source.0.source_detail.#", "1"), resource.TestCheckResourceAttr(resourceName, "source.0.source_detail.0.message_type", "ConfigurationItemChangeNotification"), resource.TestCheckResourceAttr(resourceName, "source.0.custom_policy_details.#", "1"), - resource.TestCheckResourceAttr(resourceName, "source.0.custom_policy_details.0.policy_runtime", "guard-2.x.x"), resource.TestCheckResourceAttr(resourceName, "source.0.custom_policy_details.0.enable_debug_log_delivery", "false"), - resource.TestCheckResourceAttr(resourceName, "scope.#", "0"), + resource.TestCheckResourceAttr(resourceName, "source.0.custom_policy_details.0.policy_runtime", "guard-2.x.x"), ), }, { - ResourceName: resourceName, - ImportState: true, - ImportStateVerify: true, + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"source.0.custom_policy_details.0.policy_text"}, + }, + { + Config: testAccConfigRuleConfig_ownerPolicy(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigRuleExists(ctx, resourceName, &cr), + acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "config", regexache.MustCompile("config-rule/config-rule-[0-9a-z]+$")), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestMatchResourceAttr(resourceName, "rule_id", regexache.MustCompile("config-rule-[0-9a-z]+$")), + resource.TestCheckResourceAttr(resourceName, "scope.#", "0"), + resource.TestCheckResourceAttr(resourceName, "source.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source.0.owner", "CUSTOM_POLICY"), + resource.TestCheckResourceAttr(resourceName, "source.0.source_detail.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source.0.source_detail.0.message_type", "ConfigurationItemChangeNotification"), + resource.TestCheckResourceAttr(resourceName, "source.0.custom_policy_details.#", "1"), + resource.TestCheckResourceAttr(resourceName, "source.0.custom_policy_details.0.enable_debug_log_delivery", "true"), + resource.TestCheckResourceAttr(resourceName, "source.0.custom_policy_details.0.policy_runtime", "guard-2.x.x"), + ), }, }, }) @@ -235,7 +244,7 @@ func testAccConfigRule_ownerPolicy(t *testing.T) { func testAccConfigRule_Scope_TagKey(t *testing.T) { ctx := acctest.Context(t) - var configRule configservice.ConfigRule + var configRule types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -267,7 +276,7 @@ func testAccConfigRule_Scope_TagKey(t *testing.T) { func testAccConfigRule_Scope_TagKey_Empty(t *testing.T) { ctx := acctest.Context(t) - var configRule configservice.ConfigRule + var configRule types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -289,7 +298,7 @@ func testAccConfigRule_Scope_TagKey_Empty(t *testing.T) { func testAccConfigRule_Scope_TagValue(t *testing.T) { ctx := acctest.Context(t) - var configRule configservice.ConfigRule + var configRule types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -321,7 +330,7 @@ func testAccConfigRule_Scope_TagValue(t *testing.T) { func testAccConfigRule_tags(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigRule + var cr types.ConfigRule resourceName := "aws_config_config_rule.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) @@ -332,23 +341,11 @@ func testAccConfigRule_tags(t *testing.T) { CheckDestroy: testAccCheckConfigRuleDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigRuleConfig_tags(rName, "foo", "bar", "fizz", "buzz"), - Check: resource.ComposeTestCheckFunc( - testAccCheckConfigRuleExists(ctx, resourceName, &cr), - resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), - resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar"), - resource.TestCheckResourceAttr(resourceName, "tags.fizz", "buzz"), - ), - }, - { - Config: testAccConfigRuleConfig_tags(rName, "foo", "bar2", "fizz2", "buzz2"), + Config: testAccConfigRuleConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - resource.TestCheckResourceAttr(resourceName, "tags.%", "3"), - resource.TestCheckResourceAttr(resourceName, "tags.Name", rName), - resource.TestCheckResourceAttr(resourceName, "tags.foo", "bar2"), - resource.TestCheckResourceAttr(resourceName, "tags.fizz2", "buzz2"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), }, { @@ -357,10 +354,20 @@ func testAccConfigRule_tags(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccConfigRuleConfig_basic(rName), + Config: testAccConfigRuleConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigRuleExists(ctx, resourceName, &cr), + resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), + resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), + ), + }, + { + Config: testAccConfigRuleConfig_tags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigRuleExists(ctx, resourceName, &cr), - resource.TestCheckResourceAttr(resourceName, "tags.%", "0"), + resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), + resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), }, }, @@ -369,7 +376,7 @@ func testAccConfigRule_tags(t *testing.T) { func testAccConfigRule_disappears(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigRule + var cr types.ConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_config_rule.test" @@ -391,37 +398,22 @@ func testAccConfigRule_disappears(t *testing.T) { }) } -func testAccCheckConfigRuleName(n, desired string, obj *configservice.ConfigRule) resource.TestCheckFunc { +func testAccCheckConfigRuleExists(ctx context.Context, n string, v *types.ConfigRule) 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.Attributes["name"] != *obj.ConfigRuleName { - return fmt.Errorf("Expected name: %q, given: %q", desired, *obj.ConfigRuleName) - } - return nil - } -} - -func testAccCheckConfigRuleExists(ctx context.Context, n string, obj *configservice.ConfigRule) 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 config rule ID is set") - } + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + output, err := tfconfig.FindConfigRuleByName(ctx, conn, rs.Primary.ID) - rule, err := tfconfig.FindConfigRule(ctx, conn, rs.Primary.ID) if err != nil { - return fmt.Errorf("Failed to describe config rule: %w", err) + return err } - *obj = *rule + + *v = *output return nil } @@ -429,14 +421,14 @@ func testAccCheckConfigRuleExists(ctx context.Context, n string, obj *configserv func testAccCheckConfigRuleDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_config_rule" { continue } - _, err := tfconfig.FindConfigRule(ctx, conn, rs.Primary.ID) + _, err := tfconfig.FindConfigRuleByName(ctx, conn, rs.Primary.ID) if tfresource.NotFound(err) { continue @@ -446,7 +438,7 @@ func testAccCheckConfigRuleDestroy(ctx context.Context) resource.TestCheckFunc { return err } - return fmt.Errorf("ConfigService Rule %s still exists", rs.Primary.ID) + return fmt.Errorf("ConfigService Config Rule %s still exists", rs.Primary.ID) } return nil @@ -490,9 +482,9 @@ resource "aws_iam_role_policy_attachment" "test" { } func testAccConfigRuleConfig_basic(rName string) string { - return testAccConfigRuleConfig_base(rName) + fmt.Sprintf(` + return acctest.ConfigCompose(testAccConfigRuleConfig_base(rName), fmt.Sprintf(` resource "aws_config_config_rule" "test" { - name = %q + name = %[1]q source { owner = "AWS" @@ -501,13 +493,13 @@ resource "aws_config_config_rule" "test" { depends_on = [aws_config_configuration_recorder.test] } -`, rName) +`, rName)) } -func testAccConfigRuleConfig_ownerAws(rName string) string { // nosemgrep:ci.aws-in-func-name - return testAccConfigRuleConfig_base(rName) + fmt.Sprintf(` +func testAccConfigRuleConfig_ownerAWS(rName string) string { // nosemgrep:ci.aws-in-func-name + return acctest.ConfigCompose(testAccConfigRuleConfig_base(rName), fmt.Sprintf(` resource "aws_config_config_rule" "test" { - name = %q + name = %[1]q description = "Terraform Acceptance tests" source { @@ -529,13 +521,13 @@ PARAMS depends_on = [aws_config_configuration_recorder.test] } -`, rName) +`, rName)) } -func testAccConfigRuleConfig_ownerPolicy(rName string) string { - return testAccConfigRuleConfig_base(rName) + fmt.Sprintf(` +func testAccConfigRuleConfig_ownerPolicy(rName string, enableDebugLogDelivery bool) string { + return acctest.ConfigCompose(testAccConfigRuleConfig_base(rName), fmt.Sprintf(` resource "aws_config_config_rule" "test" { - name = %q + name = %[1]q source { owner = "CUSTOM_POLICY" @@ -558,24 +550,26 @@ resource "aws_config_config_rule" "test" { supplementaryConfiguration.ContinuousBackupsDescription.pointInTimeRecoveryDescription.pointInTimeRecoveryStatus == "ENABLED" } EOF + + enable_debug_log_delivery = %[2]t } } depends_on = [aws_config_configuration_recorder.test] } -`, rName) +`, rName, enableDebugLogDelivery)) } -func testAccConfigRuleConfig_customLambda(randInt int, path string) string { - return fmt.Sprintf(` +func testAccConfigRuleConfig_customLambda(rName string) string { + return acctest.ConfigCompose(acctest.ConfigLambdaBase(rName, rName, rName), fmt.Sprintf(` resource "aws_config_config_rule" "test" { - name = "tf-acc-test-%[1]d" + name = %[1]q description = "Terraform Acceptance tests" maximum_execution_frequency = "Six_Hours" source { owner = "CUSTOM_LAMBDA" - source_identifier = aws_lambda_function.f.arn + source_identifier = aws_lambda_function.test.arn source_detail { event_source = "aws.config" @@ -589,76 +583,54 @@ resource "aws_config_config_rule" "test" { } depends_on = [ - aws_config_configuration_recorder.foo, - aws_config_delivery_channel.foo, + aws_config_configuration_recorder.test, + aws_config_delivery_channel.test, ] } -resource "aws_lambda_function" "f" { - filename = "%[2]s" - function_name = "tf_acc_lambda_awsconfig_%[1]d" +resource "aws_lambda_function" "test" { + filename = "test-fixtures/lambdatest.zip" + function_name = %[1]q role = aws_iam_role.iam_for_lambda.arn - handler = "exports.example" - runtime = "nodejs16.x" + handler = "index.handler" + runtime = "nodejs20.x" } -data "aws_partition" "current" {} - -resource "aws_lambda_permission" "p" { +resource "aws_lambda_permission" "test" { statement_id = "AllowExecutionFromConfig" action = "lambda:InvokeFunction" - function_name = aws_lambda_function.f.arn + function_name = aws_lambda_function.test.arn principal = "config.${data.aws_partition.current.dns_suffix}" } -resource "aws_iam_role" "iam_for_lambda" { - name = "tf_acc_lambda_aws_config_%[1]d" - - assume_role_policy = < 0 { - input.AccountAggregationSources = expandAccountAggregationSources(v.([]interface{})) - } + if v, ok := d.GetOk("account_aggregation_source"); ok && len(v.([]interface{})) > 0 { + input.AccountAggregationSources = expandAccountAggregationSources(v.([]interface{})) + } - if v, ok := d.GetOk("organization_aggregation_source"); ok && len(v.([]interface{})) > 0 { - input.OrganizationAggregationSource = expandOrganizationAggregationSource(v.([]interface{})[0].(map[string]interface{})) - } + if v, ok := d.GetOk("organization_aggregation_source"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + input.OrganizationAggregationSource = expandOrganizationAggregationSource(v.([]interface{})[0].(map[string]interface{})) + } - resp, err := conn.PutConfigurationAggregatorWithContext(ctx, input) - if err != nil { - return sdkdiag.AppendErrorf(diags, "creating aggregator: %s", err) - } + output, err := conn.PutConfigurationAggregator(ctx, input) - configAgg := resp.ConfigurationAggregator - d.SetId(aws.StringValue(configAgg.ConfigurationAggregatorName)) + if err != nil { + return sdkdiag.AppendErrorf(diags, "putting ConfigService Configuration Aggregator (%s): %s", name, err) + } + + if d.IsNewResource() { + d.SetId(aws.ToString(output.ConfigurationAggregator.ConfigurationAggregatorName)) + } + } return append(diags, resourceConfigurationAggregatorRead(ctx, d, meta)...) } func resourceConfigurationAggregatorRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - req := &configservice.DescribeConfigurationAggregatorsInput{ - ConfigurationAggregatorNames: []*string{aws.String(d.Id())}, - } + aggregator, err := findConfigurationAggregatorByName(ctx, conn, d.Id()) - res, err := conn.DescribeConfigurationAggregatorsWithContext(ctx, req) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationAggregatorException) { - create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameConfigurationAggregator, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Configuration Aggregator (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameConfigurationAggregator, d.Id(), err) - } - - if !d.IsNewResource() && (res == nil || len(res.ConfigurationAggregators) == 0) { - log.Printf("[WARN] No aggregators returned (%s), removing from state", d.Id()) - d.SetId("") - return diags - } - - if d.IsNewResource() && (res == nil || len(res.ConfigurationAggregators) == 0) { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameConfigurationAggregator, d.Id(), errors.New("not found after creation")) + return sdkdiag.AppendErrorf(diags, "reading ConfigService Configuration Aggregator (%s): %s", d.Id(), err) } - aggregator := res.ConfigurationAggregators[0] - arn := aws.StringValue(aggregator.ConfigurationAggregatorArn) - d.Set("arn", arn) - d.Set("name", aggregator.ConfigurationAggregatorName) - if err := d.Set("account_aggregation_source", flattenAccountAggregationSources(aggregator.AccountAggregationSources)); err != nil { return sdkdiag.AppendErrorf(diags, "setting account_aggregation_source: %s", err) } - + d.Set("arn", aggregator.ConfigurationAggregatorArn) + d.Set("name", aggregator.ConfigurationAggregatorName) if err := d.Set("organization_aggregation_source", flattenOrganizationAggregationSource(aggregator.OrganizationAggregationSource)); err != nil { return sdkdiag.AppendErrorf(diags, "setting organization_aggregation_source: %s", err) } @@ -200,20 +190,139 @@ func resourceConfigurationAggregatorRead(ctx context.Context, d *schema.Resource func resourceConfigurationAggregatorDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - req := &configservice.DeleteConfigurationAggregatorInput{ + log.Printf("[DEBUG] Deleting ConfigService Configuration Aggregator: %s", d.Id()) + _, err := conn.DeleteConfigurationAggregator(ctx, &configservice.DeleteConfigurationAggregatorInput{ ConfigurationAggregatorName: aws.String(d.Id()), - } - _, err := conn.DeleteConfigurationAggregatorWithContext(ctx, req) + }) - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationAggregatorException) { + if errs.IsA[*types.NoSuchConfigurationAggregatorException](err) { return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Config Configuration Aggregator (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Configuration Aggregator (%s): %s", d.Id(), err) } return diags } + +func findConfigurationAggregatorByName(ctx context.Context, conn *configservice.Client, name string) (*types.ConfigurationAggregator, error) { + input := &configservice.DescribeConfigurationAggregatorsInput{ + ConfigurationAggregatorNames: []string{name}, + } + + return findConfigurationAggregator(ctx, conn, input) +} + +func findConfigurationAggregator(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigurationAggregatorsInput) (*types.ConfigurationAggregator, error) { + output, err := findConfigurationAggregators(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findConfigurationAggregators(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigurationAggregatorsInput) ([]types.ConfigurationAggregator, error) { + var output []types.ConfigurationAggregator + + pages := configservice.NewDescribeConfigurationAggregatorsPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchConfigurationAggregatorException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.ConfigurationAggregators...) + } + + return output, nil +} + +func expandAccountAggregationSources(tfList []interface{}) []types.AccountAggregationSource { + if len(tfList) == 0 { + return nil + } + + var apiObjects []types.AccountAggregationSource + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + + apiObject := types.AccountAggregationSource{ + AllAwsRegions: tfMap["all_regions"].(bool), + } + + if v, ok := tfMap["account_ids"].([]interface{}); ok && len(v) > 0 { + apiObject.AccountIds = flex.ExpandStringValueList(v) + } + + if v, ok := tfMap["regions"].([]interface{}); ok && len(v) > 0 { + apiObject.AwsRegions = flex.ExpandStringValueList(v) + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func expandOrganizationAggregationSource(tfMap map[string]interface{}) *types.OrganizationAggregationSource { + if tfMap == nil { + return nil + } + + apiObject := &types.OrganizationAggregationSource{ + AllAwsRegions: tfMap["all_regions"].(bool), + RoleArn: aws.String(tfMap["role_arn"].(string)), + } + + if v, ok := tfMap["regions"].([]interface{}); ok && len(v) > 0 { + apiObject.AwsRegions = flex.ExpandStringValueList(v) + } + + return apiObject +} + +func flattenAccountAggregationSources(apiObjects []types.AccountAggregationSource) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + apiObject := apiObjects[0] + tfMap := map[string]interface{}{ + "account_ids": apiObject.AccountIds, + "all_regions": apiObject.AllAwsRegions, + "regions": apiObject.AwsRegions, + } + + return []interface{}{tfMap} +} + +func flattenOrganizationAggregationSource(apiObject *types.OrganizationAggregationSource) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "all_regions": apiObject.AllAwsRegions, + "regions": apiObject.AwsRegions, + "role_arn": aws.ToString(apiObject.RoleArn), + } + + return []interface{}{tfMap} +} diff --git a/internal/service/configservice/configuration_aggregator_test.go b/internal/service/configservice/configuration_aggregator_test.go index fb961b38e4d..5c0a7922460 100644 --- a/internal/service/configservice/configuration_aggregator_test.go +++ b/internal/service/configservice/configuration_aggregator_test.go @@ -9,20 +9,20 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func TestAccConfigServiceConfigurationAggregator_account(t *testing.T) { ctx := acctest.Context(t) - var ca configservice.ConfigurationAggregator + var ca types.ConfigurationAggregator //Name is upper case on purpose to test https://github.com/hashicorp/terraform-provider-aws/issues/8432 rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_configuration_aggregator.test" @@ -37,7 +37,6 @@ func TestAccConfigServiceConfigurationAggregator_account(t *testing.T) { Config: testAccConfigurationAggregatorConfig_account(rName), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationAggregatorExists(ctx, resourceName, &ca), - testAccCheckConfigurationAggregatorName(resourceName, rName, &ca), resource.TestCheckResourceAttr(resourceName, "name", rName), acctest.MatchResourceAttrRegionalARN(resourceName, "arn", "config", regexache.MustCompile(`config-aggregator/config-aggregator-.+`)), resource.TestCheckResourceAttr(resourceName, "account_aggregation_source.#", "1"), @@ -59,7 +58,7 @@ func TestAccConfigServiceConfigurationAggregator_account(t *testing.T) { func TestAccConfigServiceConfigurationAggregator_organization(t *testing.T) { ctx := acctest.Context(t) - var ca configservice.ConfigurationAggregator + var ca types.ConfigurationAggregator rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_configuration_aggregator.test" @@ -73,7 +72,6 @@ func TestAccConfigServiceConfigurationAggregator_organization(t *testing.T) { Config: testAccConfigurationAggregatorConfig_organization(rName), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationAggregatorExists(ctx, resourceName, &ca), - testAccCheckConfigurationAggregatorName(resourceName, rName, &ca), resource.TestCheckResourceAttr(resourceName, "name", rName), resource.TestCheckResourceAttr(resourceName, "organization_aggregation_source.#", "1"), resource.TestCheckResourceAttrPair(resourceName, "organization_aggregation_source.0.role_arn", "aws_iam_role.test", "arn"), @@ -120,7 +118,7 @@ func TestAccConfigServiceConfigurationAggregator_switch(t *testing.T) { func TestAccConfigServiceConfigurationAggregator_tags(t *testing.T) { ctx := acctest.Context(t) - var ca configservice.ConfigurationAggregator + var ca types.ConfigurationAggregator rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_configuration_aggregator.test" @@ -134,7 +132,6 @@ func TestAccConfigServiceConfigurationAggregator_tags(t *testing.T) { Config: testAccConfigurationAggregatorConfig_tags1(rName, "key1", "value1"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationAggregatorExists(ctx, resourceName, &ca), - testAccCheckConfigurationAggregatorName(resourceName, rName, &ca), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1"), ), @@ -143,7 +140,6 @@ func TestAccConfigServiceConfigurationAggregator_tags(t *testing.T) { Config: testAccConfigurationAggregatorConfig_tags2(rName, "key1", "value1updated", "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationAggregatorExists(ctx, resourceName, &ca), - testAccCheckConfigurationAggregatorName(resourceName, rName, &ca), resource.TestCheckResourceAttr(resourceName, "tags.%", "2"), resource.TestCheckResourceAttr(resourceName, "tags.key1", "value1updated"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), @@ -158,7 +154,6 @@ func TestAccConfigServiceConfigurationAggregator_tags(t *testing.T) { Config: testAccConfigurationAggregatorConfig_tags1(rName, "key2", "value2"), Check: resource.ComposeTestCheckFunc( testAccCheckConfigurationAggregatorExists(ctx, resourceName, &ca), - testAccCheckConfigurationAggregatorName(resourceName, rName, &ca), resource.TestCheckResourceAttr(resourceName, "tags.%", "1"), resource.TestCheckResourceAttr(resourceName, "tags.key2", "value2"), ), @@ -169,7 +164,7 @@ func TestAccConfigServiceConfigurationAggregator_tags(t *testing.T) { func TestAccConfigServiceConfigurationAggregator_disappears(t *testing.T) { ctx := acctest.Context(t) - var ca configservice.ConfigurationAggregator + var ca types.ConfigurationAggregator rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_configuration_aggregator.test" @@ -191,43 +186,22 @@ func TestAccConfigServiceConfigurationAggregator_disappears(t *testing.T) { }) } -func testAccCheckConfigurationAggregatorName(n, desired string, obj *configservice.ConfigurationAggregator) resource.TestCheckFunc { +func testAccCheckConfigurationAggregatorExists(ctx context.Context, n string, v *types.ConfigurationAggregator) 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.Attributes["name"] != aws.StringValue(obj.ConfigurationAggregatorName) { - return fmt.Errorf("expected name: %q, given: %q", desired, aws.StringValue(obj.ConfigurationAggregatorName)) - } - return nil - } -} -func testAccCheckConfigurationAggregatorExists(ctx context.Context, n string, obj *configservice.ConfigurationAggregator) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not Found: %s", n) - } + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - if rs.Primary.ID == "" { - return fmt.Errorf("No config configuration aggregator ID is set") - } + output, err := tfconfig.FindConfigurationAggregatorByName(ctx, conn, rs.Primary.ID) - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) - out, err := conn.DescribeConfigurationAggregatorsWithContext(ctx, &configservice.DescribeConfigurationAggregatorsInput{ - ConfigurationAggregatorNames: []*string{aws.String(rs.Primary.Attributes["name"])}, - }) if err != nil { - return fmt.Errorf("Failed to describe config configuration aggregator: %s", err) - } - if len(out.ConfigurationAggregators) < 1 { - return fmt.Errorf("No config configuration aggregator found when describing %q", rs.Primary.Attributes["name"]) + return err } - ca := out.ConfigurationAggregators[0] - *obj = *ca + *v = *output return nil } @@ -235,23 +209,24 @@ func testAccCheckConfigurationAggregatorExists(ctx context.Context, n string, ob func testAccCheckConfigurationAggregatorDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_configuration_aggregator" { continue } - resp, err := conn.DescribeConfigurationAggregatorsWithContext(ctx, &configservice.DescribeConfigurationAggregatorsInput{ - ConfigurationAggregatorNames: []*string{aws.String(rs.Primary.Attributes["name"])}, - }) + _, err := tfconfig.FindConfigurationAggregatorByName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.ConfigurationAggregators) != 0 && - aws.StringValue(resp.ConfigurationAggregators[0].ConfigurationAggregatorName) == rs.Primary.Attributes["name"] { - return fmt.Errorf("config configuration aggregator still exists: %s", rs.Primary.Attributes["name"]) - } + if tfresource.NotFound(err) { + continue } + + if err != nil { + return err + } + + return fmt.Errorf("ConfigService Configuration Aggregator %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/configservice/configuration_recorder.go b/internal/service/configservice/configuration_recorder.go index de607ee5e10..00f31019ccf 100644 --- a/internal/service/configservice/configuration_recorder.go +++ b/internal/service/configservice/configuration_recorder.go @@ -8,23 +8,24 @@ import ( "errors" "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "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" ) -// @SDKResource("aws_config_configuration_recorder") -func ResourceConfigurationRecorder() *schema.Resource { +// @SDKResource("aws_config_configuration_recorder", name="Configuration Recorder") +func resourceConfigurationRecorder() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceConfigurationRecorderPut, ReadWithoutTimeout: resourceConfigurationRecorderRead, @@ -35,16 +36,14 @@ func ResourceConfigurationRecorder() *schema.Resource { StateContext: schema.ImportStatePassthroughContext, }, - CustomizeDiff: customdiff.All( - resourceConfigCustomizeDiff, - ), + CustomizeDiff: resourceConfigurationRecorderCustomizeDiff, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, Optional: true, ForceNew: true, - Default: "default", + Default: defaultConfigurationRecorderName, ValidateFunc: validation.StringLenBetween(0, 256), }, "recording_group": { @@ -84,9 +83,9 @@ func ResourceConfigurationRecorder() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "use_only": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(configservice.RecordingStrategyType_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.RecordingStrategyType](), }, }, }, @@ -107,10 +106,10 @@ func ResourceConfigurationRecorder() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "recording_frequency": { - Type: schema.TypeString, - Optional: true, - Default: configservice.RecordingFrequencyContinuous, - ValidateFunc: validation.StringInSlice(configservice.RecordingFrequency_Values(), false), + Type: schema.TypeString, + Optional: true, + Default: types.RecordingFrequencyContinuous, + ValidateDiagFunc: enum.Validate[types.RecordingFrequency](), }, "recording_mode_override": { Type: schema.TypeList, @@ -125,9 +124,9 @@ func ResourceConfigurationRecorder() *schema.Resource { Optional: true, }, "recording_frequency": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(configservice.RecordingFrequency_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.RecordingFrequency](), }, "resource_types": { Type: schema.TypeSet, @@ -152,11 +151,11 @@ func ResourceConfigurationRecorder() *schema.Resource { func resourceConfigurationRecorderPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) name := d.Get("name").(string) input := &configservice.PutConfigurationRecorderInput{ - ConfigurationRecorder: &configservice.ConfigurationRecorder{ + ConfigurationRecorder: &types.ConfigurationRecorder{ Name: aws.String(name), RoleARN: aws.String(d.Get("role_arn").(string)), }, @@ -170,7 +169,8 @@ func resourceConfigurationRecorderPut(ctx context.Context, d *schema.ResourceDat input.ConfigurationRecorder.RecordingMode = expandRecordingMode(v.([]interface{})[0].(map[string]interface{})) } - _, err := conn.PutConfigurationRecorderWithContext(ctx, input) + _, err := conn.PutConfigurationRecorder(ctx, input) + if err != nil { return sdkdiag.AppendErrorf(diags, "putting ConfigService Configuration Recorder (%s): %s", name, err) } @@ -184,9 +184,9 @@ func resourceConfigurationRecorderPut(ctx context.Context, d *schema.ResourceDat func resourceConfigurationRecorderRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - recorder, err := FindConfigurationRecorderByName(ctx, conn, d.Id()) + recorder, err := findConfigurationRecorderByName(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { log.Printf("[WARN] ConfigService Configuration Recorder (%s) not found, removing from state", d.Id()) @@ -199,33 +199,31 @@ func resourceConfigurationRecorderRead(ctx context.Context, d *schema.ResourceDa } d.Set("name", recorder.Name) - d.Set("role_arn", recorder.RoleARN) - if recorder.RecordingGroup != nil { if err := d.Set("recording_group", flattenRecordingGroup(recorder.RecordingGroup)); err != nil { return sdkdiag.AppendErrorf(diags, "setting recording_group: %s", err) } } - if recorder.RecordingMode != nil { if err := d.Set("recording_mode", flattenRecordingMode(recorder.RecordingMode)); err != nil { return sdkdiag.AppendErrorf(diags, "setting recording_mode: %s", err) } } + d.Set("role_arn", recorder.RoleARN) return diags } func resourceConfigurationRecorderDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) log.Printf("[DEBUG] Deleting ConfigService Configuration Recorder: %s", d.Id()) - _, err := conn.DeleteConfigurationRecorderWithContext(ctx, &configservice.DeleteConfigurationRecorderInput{ + _, err := conn.DeleteConfigurationRecorder(ctx, &configservice.DeleteConfigurationRecorderInput{ ConfigurationRecorderName: aws.String(d.Id()), }) - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationRecorderException) { + if errs.IsA[*types.NoSuchConfigurationRecorderException](err) { return diags } @@ -236,55 +234,30 @@ func resourceConfigurationRecorderDelete(ctx context.Context, d *schema.Resource return diags } -func FindConfigurationRecorderByName(ctx context.Context, conn *configservice.ConfigService, name string) (*configservice.ConfigurationRecorder, error) { - input := &configservice.DescribeConfigurationRecordersInput{ - ConfigurationRecorderNames: aws.StringSlice([]string{name}), - } - - output, err := conn.DescribeConfigurationRecordersWithContext(ctx, input) - - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationRecorderException) { - return nil, &retry.NotFoundError{ - LastError: err, - LastRequest: input, - } - } - - if err != nil { - return nil, err - } - - if output == nil { - return nil, tfresource.NewEmptyResultError(input) - } - - return tfresource.AssertSinglePtrResult(output.ConfigurationRecorders) -} - -func resourceConfigCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { +func resourceConfigurationRecorderCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { if diff.Id() == "" { // New resource. - if g, ok := diff.GetOk("recording_group"); ok { - group := g.([]interface{})[0].(map[string]interface{}) + if v, ok := diff.GetOk("recording_group"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) - if h, ok := group["all_supported"]; ok { - if i, ok := group["recording_strategy"]; ok && len(i.([]interface{})) > 0 && i.([]interface{})[0] != nil { + if h, ok := tfMap["all_supported"]; ok { + if i, ok := tfMap["recording_strategy"]; ok && len(i.([]interface{})) > 0 && i.([]interface{})[0] != nil { strategy := i.([]interface{})[0].(map[string]interface{}) if j, ok := strategy["use_only"].(string); ok { - if h.(bool) && j != configservice.RecordingStrategyTypeAllSupportedResourceTypes { + if h.(bool) && j != string(types.RecordingStrategyTypeAllSupportedResourceTypes) { return errors.New(` Invalid record group strategy , all_supported must be set to true `) } - if k, ok := group["exclusion_by_resource_types"]; ok && len(k.([]interface{})) > 0 && k.([]interface{})[0] != nil { + if k, ok := tfMap["exclusion_by_resource_types"]; ok && len(k.([]interface{})) > 0 && k.([]interface{})[0] != nil { if h.(bool) { return errors.New(` Invalid record group , all_supported must be set to false when exclusion_by_resource_types is set `) } - if j != configservice.RecordingStrategyTypeExclusionByResourceTypes { + if j != string(types.RecordingStrategyTypeExclusionByResourceTypes) { return errors.New(` Invalid record group strategy , use only must be set to EXCLUSION_BY_RESOURCE_TYPES`) } - if l, ok := group["resource_types"]; ok { + if l, ok := tfMap["resource_types"]; ok { resourceTypes := flex.ExpandStringSet(l.(*schema.Set)) if len(resourceTypes) > 0 { return errors.New(` Invalid record group , resource_types must not be set when exclusion_by_resource_types is set `) @@ -292,18 +265,18 @@ func resourceConfigCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v } } - if l, ok := group["resource_types"]; ok { + if l, ok := tfMap["resource_types"]; ok { resourceTypes := flex.ExpandStringSet(l.(*schema.Set)) if len(resourceTypes) > 0 { if h.(bool) { return errors.New(` Invalid record group , all_supported must be set to false when resource_types is set `) } - if j != configservice.RecordingStrategyTypeInclusionByResourceTypes { + if j != string(types.RecordingStrategyTypeInclusionByResourceTypes) { return errors.New(` Invalid record group strategy , use only must be set to INCLUSION_BY_RESOURCE_TYPES`) } - if m, ok := group["exclusion_by_resource_types"]; ok && len(m.([]interface{})) > 0 && i.([]interface{})[0] != nil { + if m, ok := tfMap["exclusion_by_resource_types"]; ok && len(m.([]interface{})) > 0 && i.([]interface{})[0] != nil { return errors.New(` Invalid record group , exclusion_by_resource_types must not be set when resource_types is set `) } } @@ -312,16 +285,252 @@ func resourceConfigCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v } } } + } - /* - if g, ok := diff.GetOk("recording_mode"); ok { - mode := g.([]interface{})[0].(map[string]interface{}) + return nil +} - if f, ok := mode["recording_frequency"]; ok { - if f.(string) != configservice.RecordingFrequencyOneHour && f.(string) != configservice.RecordingFrequencyThreeHours && f.(string) != configservice.RecordingFrequencySixHours && f.(string) != configservice.RecordingFrequencyTwelveHours && f.(string) != configservice.RecordingFrequencyTwentyFourHours { - } - } - */ +func findConfigurationRecorderByName(ctx context.Context, conn *configservice.Client, name string) (*types.ConfigurationRecorder, error) { + input := &configservice.DescribeConfigurationRecordersInput{ + ConfigurationRecorderNames: []string{name}, } - return nil + + return findConfigurationRecorder(ctx, conn, input) +} + +func findConfigurationRecorder(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigurationRecordersInput) (*types.ConfigurationRecorder, error) { + output, err := findConfigurationRecorders(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findConfigurationRecorders(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigurationRecordersInput) ([]types.ConfigurationRecorder, error) { + output, err := conn.DescribeConfigurationRecorders(ctx, input) + + if errs.IsA[*types.NoSuchConfigurationRecorderException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ConfigurationRecorders, nil +} + +func expandRecordingGroup(tfMap map[string]interface{}) *types.RecordingGroup { + if tfMap == nil { + return nil + } + + apiObject := &types.RecordingGroup{} + + if v, ok := tfMap["all_supported"]; ok { + apiObject.AllSupported = v.(bool) + } + + if v, ok := tfMap["exclusion_by_resource_types"]; ok && len(v.([]interface{})) > 0 { + apiObject.ExclusionByResourceTypes = expandExclusionByResourceTypes(v.([]interface{})) + } + + if v, ok := tfMap["include_global_resource_types"]; ok { + apiObject.IncludeGlobalResourceTypes = v.(bool) + } + + if v, ok := tfMap["recording_strategy"]; ok && len(v.([]interface{})) > 0 { + apiObject.RecordingStrategy = expandRecordingStrategy(v.([]interface{})) + } + + if v, ok := tfMap["resource_types"]; ok && v.(*schema.Set).Len() > 0 { + apiObject.ResourceTypes = flex.ExpandStringyValueSet[types.ResourceType](v.(*schema.Set)) + } + + return apiObject +} + +func expandExclusionByResourceTypes(tfList []interface{}) *types.ExclusionByResourceTypes { + if len(tfList) == 0 { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + apiObject := &types.ExclusionByResourceTypes{} + + if v, ok := tfMap["resource_types"]; ok && v.(*schema.Set).Len() > 0 { + apiObject.ResourceTypes = flex.ExpandStringyValueSet[types.ResourceType](v.(*schema.Set)) + } + + return apiObject +} + +func expandRecordingStrategy(tfList []interface{}) *types.RecordingStrategy { + if len(tfList) == 0 { + return nil + } + + tfMap, ok := tfList[0].(map[string]interface{}) + if !ok { + return nil + } + + apiObject := &types.RecordingStrategy{} + + if v, ok := tfMap["use_only"].(string); ok { + apiObject.UseOnly = types.RecordingStrategyType(v) + } + + return apiObject +} + +func expandRecordingMode(tfMap map[string]interface{}) *types.RecordingMode { + if tfMap == nil { + return nil + } + + apiObject := &types.RecordingMode{} + + if v, ok := tfMap["recording_frequency"].(string); ok { + apiObject.RecordingFrequency = types.RecordingFrequency(v) + } + + if v, ok := tfMap["recording_mode_override"]; ok && len(v.([]interface{})) > 0 { + apiObject.RecordingModeOverrides = expandRecordingModeOverride(v.([]interface{})) + } + + return apiObject +} + +func expandRecordingModeOverride(tfList []interface{}) []types.RecordingModeOverride { + if len(tfList) == 0 { + return nil + } + + var apiObjects []types.RecordingModeOverride + + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) + if !ok { + continue + } + + apiObject := types.RecordingModeOverride{} + + if v, ok := tfMap["description"].(string); ok && v != "" { + apiObject.Description = aws.String(v) + } + + if v, ok := tfMap["recording_frequency"].(string); ok { + apiObject.RecordingFrequency = types.RecordingFrequency(v) + } + + if v, ok := tfMap["resource_types"]; ok && v.(*schema.Set).Len() > 0 { + apiObject.ResourceTypes = flex.ExpandStringyValueSet[types.ResourceType](v.(*schema.Set)) + } + + apiObjects = append(apiObjects, apiObject) + } + + return apiObjects +} + +func flattenRecordingGroup(apiObject *types.RecordingGroup) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "all_supported": apiObject.AllSupported, + "include_global_resource_types": apiObject.IncludeGlobalResourceTypes, + } + + if apiObject.ExclusionByResourceTypes != nil { + tfMap["exclusion_by_resource_types"] = flattenExclusionByResourceTypes(apiObject.ExclusionByResourceTypes) + } + + if apiObject.RecordingStrategy != nil { + tfMap["recording_strategy"] = flattenRecordingStrategy(apiObject.RecordingStrategy) + } + + if apiObject.ResourceTypes != nil { + tfMap["resource_types"] = apiObject.ResourceTypes + } + + return []interface{}{tfMap} +} + +func flattenExclusionByResourceTypes(apiObject *types.ExclusionByResourceTypes) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{} + + if apiObject.ResourceTypes != nil { + tfMap["resource_types"] = apiObject.ResourceTypes + } + + return []interface{}{tfMap} +} + +func flattenRecordingStrategy(apiObject *types.RecordingStrategy) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "use_only": apiObject.UseOnly, + } + + return []interface{}{tfMap} +} + +func flattenRecordingMode(apiObject *types.RecordingMode) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "recording_frequency": apiObject.RecordingFrequency, + } + + if apiObject.RecordingModeOverrides != nil && len(apiObject.RecordingModeOverrides) > 0 { + tfMap["recording_mode_override"] = flattenRecordingModeOverrides(apiObject.RecordingModeOverrides) + } + + return []interface{}{tfMap} +} + +func flattenRecordingModeOverrides(apiObjects []types.RecordingModeOverride) []interface{} { + if len(apiObjects) == 0 { + return nil + } + + var tfList []interface{} + + for _, apiObject := range apiObjects { + m := map[string]interface{}{ + "description": aws.ToString(apiObject.Description), + "recording_frequency": apiObject.RecordingFrequency, + "resource_types": apiObject.ResourceTypes, + } + + tfList = append(tfList, m) + } + + return tfList } diff --git a/internal/service/configservice/configuration_recorder_status.go b/internal/service/configservice/configuration_recorder_status.go index 6e481adc1fe..8edf0089780 100644 --- a/internal/service/configservice/configuration_recorder_status.go +++ b/internal/service/configservice/configuration_recorder_status.go @@ -5,22 +5,22 @@ package configservice import ( "context" - "errors" "log" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" - "github.com/hashicorp/terraform-provider-aws/names" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" ) -// @SDKResource("aws_config_configuration_recorder_status") -func ResourceConfigurationRecorderStatus() *schema.Resource { +// @SDKResource("aws_config_configuration_recorder_status", name="Configuration Recorder Status") +func resourceConfigurationRecorderStatus() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceConfigurationRecorderStatusPut, ReadWithoutTimeout: resourceConfigurationRecorderStatusRead, @@ -28,108 +28,133 @@ func ResourceConfigurationRecorderStatus() *schema.Resource { DeleteWithoutTimeout: resourceConfigurationRecorderStatusDelete, Importer: &schema.ResourceImporter{ - StateContext: func(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - d.Set("name", d.Id()) - return []*schema.ResourceData{d}, nil - }, + StateContext: schema.ImportStatePassthroughContext, }, Schema: map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Required: true, - }, "is_enabled": { Type: schema.TypeBool, Required: true, }, + "name": { + Type: schema.TypeString, + Required: true, + }, }, } } func resourceConfigurationRecorderStatusPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) name := d.Get("name").(string) - d.SetId(name) if d.HasChange("is_enabled") { - isEnabled := d.Get("is_enabled").(bool) - if isEnabled { - log.Printf("[DEBUG] Starting AWSConfig Configuration recorder %q", name) - startInput := configservice.StartConfigurationRecorderInput{ + if d.Get("is_enabled").(bool) { + input := &configservice.StartConfigurationRecorderInput{ ConfigurationRecorderName: aws.String(name), } - _, err := conn.StartConfigurationRecorderWithContext(ctx, &startInput) + + _, err := conn.StartConfigurationRecorder(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Failed to start Configuration Recorder: %s", err) + return sdkdiag.AppendErrorf(diags, "starting ConfigService Configuration Recorder (%s): %s", name, err) } } else { - log.Printf("[DEBUG] Stopping AWSConfig Configuration recorder %q", name) - stopInput := configservice.StopConfigurationRecorderInput{ + input := &configservice.StopConfigurationRecorderInput{ ConfigurationRecorderName: aws.String(name), } - _, err := conn.StopConfigurationRecorderWithContext(ctx, &stopInput) + + _, err := conn.StopConfigurationRecorder(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Failed to stop Configuration Recorder: %s", err) + return sdkdiag.AppendErrorf(diags, "stopping ConfigService Configuration Recorder (%s): %s", name, err) } } } + d.SetId(name) + return append(diags, resourceConfigurationRecorderStatusRead(ctx, d, meta)...) } func resourceConfigurationRecorderStatusRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - name := d.Id() - statusInput := configservice.DescribeConfigurationRecorderStatusInput{ - ConfigurationRecorderNames: []*string{aws.String(name)}, - } - statusOut, err := conn.DescribeConfigurationRecorderStatusWithContext(ctx, &statusInput) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigurationRecorderException) { - create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorderStatus, d.Id()) + recorderStatus, err := findConfigurationRecorderStatusByName(ctx, conn, d.Id()) + + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Configuration Recorder Status (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorderStatus, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading ConfigService Configuration Recorder Status (%s): %s", d.Id(), err) } - numberOfStatuses := len(statusOut.ConfigurationRecordersStatus) - if !d.IsNewResource() && numberOfStatuses < 1 { - create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorderStatus, d.Id()) - d.SetId("") + d.Set("is_enabled", recorderStatus.Recording) + d.Set("name", d.Id()) + + return diags +} + +func resourceConfigurationRecorderStatusDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + _, err := conn.StopConfigurationRecorder(ctx, &configservice.StopConfigurationRecorderInput{ + ConfigurationRecorderName: aws.String(d.Id()), + }) + + if errs.IsA[*types.NoSuchConfigurationRecorderException](err) { return diags } - if d.IsNewResource() && numberOfStatuses < 1 { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameConfigurationRecorderStatus, d.Id(), errors.New("not found after creation")) + if err != nil { + return sdkdiag.AppendErrorf(diags, "stopping ConfigService Configuration Recorder (%s): %s", d.Id(), err) } - if numberOfStatuses > 1 { - return sdkdiag.AppendErrorf(diags, "Expected exactly 1 Configuration Recorder (status), received %d: %#v", - numberOfStatuses, statusOut.ConfigurationRecordersStatus) + return diags +} + +func findConfigurationRecorderStatusByName(ctx context.Context, conn *configservice.Client, name string) (*types.ConfigurationRecorderStatus, error) { + input := &configservice.DescribeConfigurationRecorderStatusInput{ + ConfigurationRecorderNames: []string{name}, } - d.Set("is_enabled", statusOut.ConfigurationRecordersStatus[0].Recording) + return findConfigurationRecorderStatus(ctx, conn, input) +} - return diags +func findConfigurationRecorderStatus(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigurationRecorderStatusInput) (*types.ConfigurationRecorderStatus, error) { + output, err := findConfigurationRecorderStatuses(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) } -func resourceConfigurationRecorderStatusDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) - input := configservice.StopConfigurationRecorderInput{ - ConfigurationRecorderName: aws.String(d.Get("name").(string)), +func findConfigurationRecorderStatuses(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConfigurationRecorderStatusInput) ([]types.ConfigurationRecorderStatus, error) { + output, err := conn.DescribeConfigurationRecorderStatus(ctx, input) + + if errs.IsA[*types.NoSuchConfigurationRecorderException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } } - _, err := conn.StopConfigurationRecorderWithContext(ctx, &input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "Stopping Configuration Recorder failed: %s", err) + return nil, err } - return diags + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.ConfigurationRecordersStatus, nil } diff --git a/internal/service/configservice/configuration_recorder_status_test.go b/internal/service/configservice/configuration_recorder_status_test.go index 9bd609e07ff..25a559d1afd 100644 --- a/internal/service/configservice/configuration_recorder_status_test.go +++ b/internal/service/configservice/configuration_recorder_status_test.go @@ -8,22 +8,22 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccConfigurationRecorderStatus_basic(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigurationRecorder - var crs configservice.ConfigurationRecorderStatus - rInt := sdkacctest.RandInt() - expectedName := fmt.Sprintf("tf-acc-test-%d", rInt) + var crs types.ConfigurationRecorderStatus + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_config_configuration_recorder_status.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -32,25 +32,27 @@ func testAccConfigurationRecorderStatus_basic(t *testing.T) { CheckDestroy: testAccCheckConfigurationRecorderStatusDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigurationRecorderStatusConfig_basic(rInt, false), + Config: testAccConfigurationRecorderStatusConfig_basic(rName, false), Check: resource.ComposeTestCheckFunc( - testAccCheckConfigurationRecorderExists(ctx, "aws_config_configuration_recorder.foo", &cr), - testAccCheckConfigurationRecorderStatusExists(ctx, "aws_config_configuration_recorder_status.foo", &crs), - testAccCheckConfigurationRecorderStatus("aws_config_configuration_recorder_status.foo", false, &crs), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "is_enabled", "false"), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "name", expectedName), + testAccCheckConfigurationRecorderStatusExists(ctx, resourceName, &crs), + resource.TestCheckResourceAttr(resourceName, "is_enabled", "false"), + resource.TestCheckResourceAttr(resourceName, "name", rName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } -func testAccConfigurationRecorderStatus_startEnabled(t *testing.T) { +func testAccConfigurationRecorderStatus_disappears(t *testing.T) { ctx := acctest.Context(t) - var cr configservice.ConfigurationRecorder - var crs configservice.ConfigurationRecorderStatus - rInt := sdkacctest.RandInt() - expectedName := fmt.Sprintf("tf-acc-test-%d", rInt) + var crs types.ConfigurationRecorderStatus + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_config_configuration_recorder_status.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -59,43 +61,22 @@ func testAccConfigurationRecorderStatus_startEnabled(t *testing.T) { CheckDestroy: testAccCheckConfigurationRecorderStatusDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigurationRecorderStatusConfig_basic(rInt, true), + Config: testAccConfigurationRecorderStatusConfig_basic(rName, false), Check: resource.ComposeTestCheckFunc( - testAccCheckConfigurationRecorderExists(ctx, "aws_config_configuration_recorder.foo", &cr), - testAccCheckConfigurationRecorderStatusExists(ctx, "aws_config_configuration_recorder_status.foo", &crs), - testAccCheckConfigurationRecorderStatus("aws_config_configuration_recorder_status.foo", true, &crs), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "is_enabled", "true"), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "name", expectedName), - ), - }, - { - Config: testAccConfigurationRecorderStatusConfig_basic(rInt, false), - Check: resource.ComposeTestCheckFunc( - testAccCheckConfigurationRecorderExists(ctx, "aws_config_configuration_recorder.foo", &cr), - testAccCheckConfigurationRecorderStatusExists(ctx, "aws_config_configuration_recorder_status.foo", &crs), - testAccCheckConfigurationRecorderStatus("aws_config_configuration_recorder_status.foo", false, &crs), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "is_enabled", "false"), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "name", expectedName), - ), - }, - { - Config: testAccConfigurationRecorderStatusConfig_basic(rInt, true), - Check: resource.ComposeTestCheckFunc( - testAccCheckConfigurationRecorderExists(ctx, "aws_config_configuration_recorder.foo", &cr), - testAccCheckConfigurationRecorderStatusExists(ctx, "aws_config_configuration_recorder_status.foo", &crs), - testAccCheckConfigurationRecorderStatus("aws_config_configuration_recorder_status.foo", true, &crs), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "is_enabled", "true"), - resource.TestCheckResourceAttr("aws_config_configuration_recorder_status.foo", "name", expectedName), + testAccCheckConfigurationRecorderStatusExists(ctx, resourceName, &crs), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfconfig.ResourceConfigurationRecorder(), "aws_config_configuration_recorder.test"), ), + ExpectNonEmptyPlan: true, }, }, }) } -func testAccConfigurationRecorderStatus_importBasic(t *testing.T) { +func testAccConfigurationRecorderStatus_startEnabled(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_config_configuration_recorder_status.foo" - rInt := sdkacctest.RandInt() + var crs types.ConfigurationRecorderStatus + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_config_configuration_recorder_status.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -104,54 +85,51 @@ func testAccConfigurationRecorderStatus_importBasic(t *testing.T) { CheckDestroy: testAccCheckConfigurationRecorderStatusDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccConfigurationRecorderStatusConfig_basic(rInt, true), + Config: testAccConfigurationRecorderStatusConfig_basic(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationRecorderStatusExists(ctx, resourceName, &crs), + resource.TestCheckResourceAttr(resourceName, "is_enabled", "true"), + ), }, - { ResourceName: resourceName, ImportState: true, ImportStateVerify: true, }, + { + Config: testAccConfigurationRecorderStatusConfig_basic(rName, false), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationRecorderStatusExists(ctx, resourceName, &crs), + resource.TestCheckResourceAttr(resourceName, "is_enabled", "false"), + ), + }, + { + Config: testAccConfigurationRecorderStatusConfig_basic(rName, true), + Check: resource.ComposeTestCheckFunc( + testAccCheckConfigurationRecorderStatusExists(ctx, resourceName, &crs), + resource.TestCheckResourceAttr(resourceName, "is_enabled", "true"), + ), + }, }, }) } -func testAccCheckConfigurationRecorderStatusExists(ctx context.Context, n string, obj *configservice.ConfigurationRecorderStatus) resource.TestCheckFunc { +func testAccCheckConfigurationRecorderStatusExists(ctx context.Context, n string, v *types.ConfigurationRecorderStatus) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) - out, err := conn.DescribeConfigurationRecorderStatusWithContext(ctx, &configservice.DescribeConfigurationRecorderStatusInput{ - ConfigurationRecorderNames: []*string{aws.String(rs.Primary.Attributes["name"])}, - }) - if err != nil { - return fmt.Errorf("Failed to describe status of configuration recorder: %s", err) - } - if len(out.ConfigurationRecordersStatus) < 1 { - return fmt.Errorf("Configuration Recorder %q not found", rs.Primary.Attributes["name"]) - } + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - status := out.ConfigurationRecordersStatus[0] - *obj = *status + output, err := tfconfig.FindConfigurationRecorderStatusByName(ctx, conn, rs.Primary.ID) - return nil - } -} - -func testAccCheckConfigurationRecorderStatus(n string, desired bool, obj *configservice.ConfigurationRecorderStatus) resource.TestCheckFunc { - return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found: %s", n) + if err != nil { + return err } - if *obj.Recording != desired { - return fmt.Errorf("Expected configuration recorder %q recording to be %t, given: %t", - n, desired, *obj.Recording) - } + *v = *output return nil } @@ -159,39 +137,39 @@ func testAccCheckConfigurationRecorderStatus(n string, desired bool, obj *config func testAccCheckConfigurationRecorderStatusDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_configuration_recorder_status" { continue } - resp, err := conn.DescribeConfigurationRecorderStatusWithContext(ctx, &configservice.DescribeConfigurationRecorderStatusInput{ - ConfigurationRecorderNames: []*string{aws.String(rs.Primary.Attributes["name"])}, - }) + _, err := tfconfig.FindConfigurationRecorderStatusByName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.ConfigurationRecordersStatus) != 0 && - *resp.ConfigurationRecordersStatus[0].Name == rs.Primary.Attributes["name"] && - *resp.ConfigurationRecordersStatus[0].Recording { - return fmt.Errorf("Configuration recorder is still recording: %s", rs.Primary.Attributes["name"]) - } + if tfresource.NotFound(err) { + continue } + + if err != nil { + return err + } + + return fmt.Errorf("ConfigService Configuration Recorder Status %s still exists", rs.Primary.ID) } return nil } } -func testAccConfigurationRecorderStatusConfig_basic(randInt int, enabled bool) string { +func testAccConfigurationRecorderStatusConfig_basic(rName string, enabled bool) string { return fmt.Sprintf(` -resource "aws_config_configuration_recorder" "foo" { - name = "tf-acc-test-%d" - role_arn = aws_iam_role.r.arn +resource "aws_config_configuration_recorder" "test" { + name = %[1]q + role_arn = aws_iam_role.test.arn } -resource "aws_iam_role" "r" { - name = "tf-acc-test-awsconfig-%d" +resource "aws_iam_role" "test" { + name = %[1]q assume_role_policy = < 0 { + input.ConformancePackInputParameters = expandConformancePackInputParameters(v.(*schema.Set).List()) } if v, ok := d.GetOk("template_body"); ok { @@ -129,15 +131,21 @@ func resourceConformancePackPut(ctx context.Context, d *schema.ResourceData, met input.TemplateS3Uri = aws.String(v.(string)) } - _, err := conn.PutConformancePackWithContext(ctx, &input) + _, err := conn.PutConformancePack(ctx, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Config Conformance Pack (%s): %s", name, err) + return sdkdiag.AppendErrorf(diags, "putting ConfigService Conformance Pack (%s): %s", name, err) } - d.SetId(name) + if d.IsNewResource() { + d.SetId(name) + } - if err := waitForConformancePackStateCreateComplete(ctx, conn, d.Id()); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Config Conformance Pack (%s) to be created: %s", d.Id(), err) + const ( + timeout = 5 * time.Minute + ) + if _, err := waitConformancePackCreated(ctx, conn, d.Id(), timeout); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Conformance Pack (%s) create: %s", d.Id(), err) } return append(diags, resourceConformancePackRead(ctx, d, meta)...) @@ -145,131 +153,242 @@ func resourceConformancePackPut(ctx context.Context, d *schema.ResourceData, met func resourceConformancePackRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - pack, err := DescribeConformancePack(ctx, conn, d.Id()) + pack, err := findConformancePackByName(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConformancePackException) { - log.Printf("[WARN] Config Conformance Pack (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Conformance Pack (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "describing Config Conformance Pack (%s): %s", d.Id(), err) - } - - if pack == nil { - if d.IsNewResource() { - return sdkdiag.AppendErrorf(diags, "describing Config Conformance Pack (%s): not found", d.Id()) - } - - log.Printf("[WARN] Config Conformance Pack (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags + return sdkdiag.AppendErrorf(diags, "reading ConfigService Conformance Pack (%s): %s", d.Id(), err) } d.Set("arn", pack.ConformancePackArn) d.Set("delivery_s3_bucket", pack.DeliveryS3Bucket) d.Set("delivery_s3_key_prefix", pack.DeliveryS3KeyPrefix) - d.Set("name", pack.ConformancePackName) - - if err = d.Set("input_parameter", flattenConfigConformancePackInputParameters(pack.ConformancePackInputParameters)); err != nil { + if err = d.Set("input_parameter", flattenConformancePackInputParameters(pack.ConformancePackInputParameters)); err != nil { return sdkdiag.AppendErrorf(diags, "setting input_parameter: %s", err) } + d.Set("name", pack.ConformancePackName) return diags } func resourceConformancePackDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + const ( + timeout = 5 * time.Minute + ) + log.Printf("[DEBUG] Deleting ConfigService Conformance Pack: %s", d.Id()) + _, err := tfresource.RetryWhenIsA[*types.ResourceInUseException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteConformancePack(ctx, &configservice.DeleteConformancePackInput{ + ConformancePackName: aws.String(d.Id()), + }) + }) + + if errs.IsA[*types.NoSuchConformancePackException](err) { + return diags + } - input := &configservice.DeleteConformancePackInput{ - ConformancePackName: aws.String(d.Id()), + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Conformance Pack (%s): %s", d.Id(), err) } - err := retry.RetryContext(ctx, conformancePackDeleteTimeout, func() *retry.RetryError { - _, err := conn.DeleteConformancePackWithContext(ctx, input) + if _, err := waitConformancePackDeleted(ctx, conn, d.Id(), timeout); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Conformance Pack (%s) delete: %s", d.Id(), err) + } - if err != nil { - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeResourceInUseException) { - return retry.RetryableError(err) + return diags +} + +func findConformancePackByName(ctx context.Context, conn *configservice.Client, name string) (*types.ConformancePackDetail, error) { + input := &configservice.DescribeConformancePacksInput{ + ConformancePackNames: []string{name}, + } + + return findConformancePack(ctx, conn, input) +} + +func findConformancePack(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConformancePacksInput) (*types.ConformancePackDetail, error) { + output, err := findConformancePacks(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findConformancePacks(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConformancePacksInput) ([]types.ConformancePackDetail, error) { + var output []types.ConformancePackDetail + + pages := configservice.NewDescribeConformancePacksPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchConformancePackException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } + } - return retry.NonRetryableError(err) + if err != nil { + return nil, err } - return nil - }) + output = append(output, page.ConformancePackDetails...) + } + + return output, nil +} - if tfresource.TimedOut(err) { - _, err = conn.DeleteConformancePackWithContext(ctx, input) +func findConformancePackStatusByName(ctx context.Context, conn *configservice.Client, name string) (*types.ConformancePackStatusDetail, error) { + input := &configservice.DescribeConformancePackStatusInput{ + ConformancePackNames: []string{name}, } + return findConformancePackStatus(ctx, conn, input) +} + +func findConformancePackStatus(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConformancePackStatusInput) (*types.ConformancePackStatusDetail, error) { + output, err := findConformancePackStatuses(ctx, conn, input) + if err != nil { - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConformancePackException) { - return diags + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findConformancePackStatuses(ctx context.Context, conn *configservice.Client, input *configservice.DescribeConformancePackStatusInput) ([]types.ConformancePackStatusDetail, error) { + var output []types.ConformancePackStatusDetail + + pages := configservice.NewDescribeConformancePackStatusPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchConformancePackException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err } - return sdkdiag.AppendErrorf(diags, "erorr deleting Config Conformance Pack (%s): %s", d.Id(), err) + output = append(output, page.ConformancePackStatusDetails...) } - if err := waitForConformancePackStateDeleteComplete(ctx, conn, d.Id()); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Config Conformance Pack (%s) to be deleted: %s", d.Id(), err) + return output, nil +} + +func statusConformancePack(ctx context.Context, conn *configservice.Client, name string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findConformancePackStatusByName(ctx, conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.ConformancePackState), err + } +} + +func waitConformancePackCreated(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.ConformancePackStatusDetail, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ConformancePackStateCreateInProgress), + Target: enum.Slice(types.ConformancePackStateCreateComplete), + Refresh: statusConformancePack(ctx, conn, name), + Timeout: timeout, } - return diags + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.ConformancePackStatusDetail); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.ConformancePackStatusReason))) + + return output, err + } + + return nil, err } -func expandConfigConformancePackInputParameters(l []interface{}) []*configservice.ConformancePackInputParameter { - if len(l) == 0 || l[0] == nil { +func waitConformancePackDeleted(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.ConformancePackStatusDetail, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.ConformancePackStateDeleteInProgress), + Target: []string{}, + Refresh: statusConformancePack(ctx, conn, name), + Timeout: timeout, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.ConformancePackStatusDetail); ok { + tfresource.SetLastError(err, errors.New(aws.ToString(output.ConformancePackStatusReason))) + + return output, err + } + + return nil, err +} + +func expandConformancePackInputParameters(tfList []interface{}) []types.ConformancePackInputParameter { + if len(tfList) == 0 { return nil } - params := make([]*configservice.ConformancePackInputParameter, 0, len(l)) + var apiObjects []types.ConformancePackInputParameter - for _, v := range l { - tfMap, ok := v.(map[string]interface{}) + for _, tfMapRaw := range tfList { + tfMap, ok := tfMapRaw.(map[string]interface{}) if !ok { continue } - param := &configservice.ConformancePackInputParameter{} + apiObject := types.ConformancePackInputParameter{} - if name, ok := tfMap["parameter_name"].(string); ok && name != "" { - param.ParameterName = aws.String(name) + if v, ok := tfMap["parameter_name"].(string); ok && v != "" { + apiObject.ParameterName = aws.String(v) } - if value, ok := tfMap["parameter_value"].(string); ok && value != "" { - param.ParameterValue = aws.String(value) + if v, ok := tfMap["parameter_value"].(string); ok && v != "" { + apiObject.ParameterValue = aws.String(v) } - params = append(params, param) + apiObjects = append(apiObjects, apiObject) } - return params + return apiObjects } -func flattenConfigConformancePackInputParameters(parameters []*configservice.ConformancePackInputParameter) []interface{} { - if parameters == nil { +func flattenConformancePackInputParameters(apiObjects []types.ConformancePackInputParameter) []interface{} { + if len(apiObjects) == 0 { return nil } - params := make([]interface{}, 0, len(parameters)) - - for _, p := range parameters { - if p == nil { - continue - } + var tfList []interface{} - param := map[string]interface{}{ - "parameter_name": aws.StringValue(p.ParameterName), - "parameter_value": aws.StringValue(p.ParameterValue), + for _, apiObject := range apiObjects { + tfMap := map[string]interface{}{ + "parameter_name": aws.ToString(apiObject.ParameterName), + "parameter_value": aws.ToString(apiObject.ParameterValue), } - params = append(params, param) + tfList = append(tfList, tfMap) } - return params + return tfList } diff --git a/internal/service/configservice/conformance_pack_test.go b/internal/service/configservice/conformance_pack_test.go index 98887602254..65d7803583b 100644 --- a/internal/service/configservice/conformance_pack_test.go +++ b/internal/service/configservice/conformance_pack_test.go @@ -5,25 +5,26 @@ package configservice_test import ( "context" + "errors" "fmt" "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccConformancePack_basic(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -58,7 +59,7 @@ func testAccConformancePack_basic(t *testing.T) { func testAccConformancePack_updateName(t *testing.T) { ctx := acctest.Context(t) - var before, after configservice.ConformancePackDetail + var before, after types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) rNameUpdated := sdkacctest.RandomWithPrefix("tf-acc-test-update") resourceName := "aws_config_conformance_pack.test" @@ -102,7 +103,7 @@ func testAccConformancePack_updateName(t *testing.T) { func testAccConformancePack_disappears(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -126,7 +127,7 @@ func testAccConformancePack_disappears(t *testing.T) { func testAccConformancePack_inputParameters(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -163,7 +164,7 @@ func testAccConformancePack_inputParameters(t *testing.T) { func testAccConformancePack_S3Delivery(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -196,7 +197,7 @@ func testAccConformancePack_S3Delivery(t *testing.T) { func testAccConformancePack_S3Template(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -229,7 +230,7 @@ func testAccConformancePack_S3Template(t *testing.T) { func testAccConformancePack_updateInputParameters(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -279,7 +280,7 @@ func testAccConformancePack_updateInputParameters(t *testing.T) { func testAccConformancePack_updateS3Delivery(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -319,7 +320,7 @@ func testAccConformancePack_updateS3Delivery(t *testing.T) { func testAccConformancePack_updateS3Template(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -359,7 +360,7 @@ func testAccConformancePack_updateS3Template(t *testing.T) { func testAccConformancePack_updateTemplateBody(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -400,7 +401,7 @@ func testAccConformancePack_updateTemplateBody(t *testing.T) { func testAccConformancePack_S3TemplateAndTemplateBody(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.ConformancePackDetail + var pack types.ConformancePackDetail rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_conformance_pack.test" @@ -436,67 +437,61 @@ func testAccConformancePack_S3TemplateAndTemplateBody(t *testing.T) { func testAccCheckConformancePackDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_conformance_pack" { continue } - pack, err := tfconfig.DescribeConformancePack(ctx, conn, rs.Primary.ID) + _, err := tfconfig.FindConformancePackByName(ctx, conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConformancePackException) { + if tfresource.NotFound(err) { continue } if err != nil { - return fmt.Errorf("error describing Config Conformance Pack (%s): %w", rs.Primary.ID, err) + return err } - if pack != nil { - return fmt.Errorf("Config Conformance Pack (%s) still exists", rs.Primary.ID) - } + return fmt.Errorf("ConfigService Conformance Pack %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckConformancePackExists(ctx context.Context, resourceName string, detail *configservice.ConformancePackDetail) resource.TestCheckFunc { +func testAccCheckConformancePackExists(ctx context.Context, n string, v *types.ConformancePackDetail) 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) } - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - pack, err := tfconfig.DescribeConformancePack(ctx, conn, rs.Primary.ID) + output, err := tfconfig.FindConformancePackByName(ctx, conn, rs.Primary.ID) if err != nil { - return fmt.Errorf("error describing Config Conformance Pack (%s): %w", rs.Primary.ID, err) - } - - if pack == nil { - return fmt.Errorf("Config Conformance Pack (%s) not found", rs.Primary.ID) + return err } - *detail = *pack + *v = *output return nil } } -func testAccCheckConformancePackRecreated(before, after *configservice.ConformancePackDetail) resource.TestCheckFunc { +func testAccCheckConformancePackRecreated(before, after *types.ConformancePackDetail) resource.TestCheckFunc { return func(s *terraform.State) error { - if aws.StringValue(before.ConformancePackArn) == aws.StringValue(after.ConformancePackArn) { - return fmt.Errorf("AWS Config Conformance Pack was not recreated") + if aws.ToString(before.ConformancePackArn) == aws.ToString(after.ConformancePackArn) { + return errors.New("ConfigService Conformance Pack was not recreated") } return nil } } -func testAccConformancePackConfigBase(rName string) string { +func testAccConformancePackConfig_base(rName string) string { return fmt.Sprintf(` data "aws_partition" "current" {} @@ -534,7 +529,7 @@ resource "aws_iam_role_policy_attachment" "test" { } func testAccConformancePackConfig_basic(rName string) string { - return acctest.ConfigCompose(testAccConformancePackConfigBase(rName), + return acctest.ConfigCompose(testAccConformancePackConfig_base(rName), fmt.Sprintf(` resource "aws_config_conformance_pack" "test" { depends_on = [aws_config_configuration_recorder.test] @@ -554,7 +549,7 @@ EOT } func testAccConformancePackConfig_update(rName string) string { - return acctest.ConfigCompose(testAccConformancePackConfigBase(rName), + return acctest.ConfigCompose(testAccConformancePackConfig_base(rName), fmt.Sprintf(` resource "aws_config_conformance_pack" "test" { depends_on = [aws_config_configuration_recorder.test] @@ -574,7 +569,7 @@ EOT } func testAccConformancePackConfig_inputParameter(rName, pName, pValue string) string { - return acctest.ConfigCompose(testAccConformancePackConfigBase(rName), + return acctest.ConfigCompose(testAccConformancePackConfig_base(rName), fmt.Sprintf(` resource "aws_config_conformance_pack" "test" { depends_on = [aws_config_configuration_recorder.test] @@ -603,7 +598,7 @@ EOT } func testAccConformancePackConfig_updateInputParameter(rName, pName1, pName2 string) string { - return acctest.ConfigCompose(testAccConformancePackConfigBase(rName), + return acctest.ConfigCompose(testAccConformancePackConfig_base(rName), fmt.Sprintf(` resource "aws_config_conformance_pack" "test" { depends_on = [ @@ -640,7 +635,7 @@ EOT } func testAccConformancePackConfig_s3Delivery(rName, bucketName string) string { - return acctest.ConfigCompose(testAccConformancePackConfigBase(rName), + return acctest.ConfigCompose(testAccConformancePackConfig_base(rName), fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -667,7 +662,7 @@ EOT } func testAccConformancePackConfig_s3Template(rName, bucketName string) string { - return acctest.ConfigCompose(testAccConformancePackConfigBase(rName), + return acctest.ConfigCompose(testAccConformancePackConfig_base(rName), fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -698,7 +693,7 @@ resource "aws_config_conformance_pack" "test" { } func testAccConformancePackConfig_s3TemplateAndTemplateBody(rName string) string { - return acctest.ConfigCompose(testAccConformancePackConfigBase(rName), + return acctest.ConfigCompose(testAccConformancePackConfig_base(rName), fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q diff --git a/internal/service/configservice/consts.go b/internal/service/configservice/consts.go index d2db999666a..cb52659ae2a 100644 --- a/internal/service/configservice/consts.go +++ b/internal/service/configservice/consts.go @@ -8,16 +8,11 @@ import ( ) const ( - propagationTimeout = 2 * time.Minute + organizationsPropagationTimeout = 1 * time.Minute // Organizations eventual consistency. + propagationTimeout = 2 * time.Minute // IAM eventual consistency. ) const ( - ResNameAggregateAuthorization = "Aggregate Authorization" - ResNameConfigurationAggregator = "Configuration Aggregator" - ResNameConfigurationRecorderStatus = "Configuration Recorder Status" - ResNameConfigurationRecorder = "Configuration Recorder" - ResNameDeliveryChannel = "Delivery Channel" - ResNameOrganizationManagedRule = "Organization Managed Rule" - ResNameOrganizationCustomRule = "Organization Custom Rule" - ResNameRemediationConfiguration = "Remediation Configuration" + defaultConfigurationRecorderName = "default" + defaultDeliveryChannelName = "default" ) diff --git a/internal/service/configservice/delivery_channel.go b/internal/service/configservice/delivery_channel.go index 810bf35c03b..34e01a072a2 100644 --- a/internal/service/configservice/delivery_channel.go +++ b/internal/service/configservice/delivery_channel.go @@ -5,26 +5,26 @@ package configservice import ( "context" - "errors" + "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "github.com/hashicorp/terraform-provider-aws/internal/errs/sdkdiag" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/internal/verify" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_config_delivery_channel") -func ResourceDeliveryChannel() *schema.Resource { +// @SDKResource("aws_config_delivery_channel", name="Delivery Channel") +func resourceDeliveryChannel() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceDeliveryChannelPut, ReadWithoutTimeout: resourceDeliveryChannelRead, @@ -40,7 +40,7 @@ func ResourceDeliveryChannel() *schema.Resource { Type: schema.TypeString, Optional: true, ForceNew: true, - Default: "default", + Default: defaultDeliveryChannelName, ValidateFunc: validation.StringLenBetween(0, 256), }, "s3_bucket_name": { @@ -56,11 +56,6 @@ func ResourceDeliveryChannel() *schema.Resource { Optional: true, ValidateFunc: verify.ValidARN, }, - "sns_topic_arn": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: verify.ValidARN, - }, "snapshot_delivery_properties": { Type: schema.TypeList, Optional: true, @@ -68,147 +63,171 @@ func ResourceDeliveryChannel() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "delivery_frequency": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validExecutionFrequency(), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.MaximumExecutionFrequency](), }, }, }, }, + "sns_topic_arn": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: verify.ValidARN, + }, }, } } func resourceDeliveryChannelPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) name := d.Get("name").(string) - channel := configservice.DeliveryChannel{ - Name: aws.String(name), - S3BucketName: aws.String(d.Get("s3_bucket_name").(string)), + input := &configservice.PutDeliveryChannelInput{ + DeliveryChannel: &types.DeliveryChannel{ + Name: aws.String(name), + S3BucketName: aws.String(d.Get("s3_bucket_name").(string)), + }, } if v, ok := d.GetOk("s3_key_prefix"); ok { - channel.S3KeyPrefix = aws.String(v.(string)) + input.DeliveryChannel.S3KeyPrefix = aws.String(v.(string)) } + if v, ok := d.GetOk("s3_kms_key_arn"); ok { - channel.S3KmsKeyArn = aws.String(v.(string)) - } - if v, ok := d.GetOk("sns_topic_arn"); ok { - channel.SnsTopicARN = aws.String(v.(string)) + input.DeliveryChannel.S3KmsKeyArn = aws.String(v.(string)) } - if p, ok := d.GetOk("snapshot_delivery_properties"); ok { - propertiesBlocks := p.([]interface{}) - block := propertiesBlocks[0].(map[string]interface{}) + if v, ok := d.GetOk("snapshot_delivery_properties"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + tfMap := v.([]interface{})[0].(map[string]interface{}) - if v, ok := block["delivery_frequency"]; ok { - channel.ConfigSnapshotDeliveryProperties = &configservice.ConfigSnapshotDeliveryProperties{ - DeliveryFrequency: aws.String(v.(string)), + if v, ok := tfMap["delivery_frequency"]; ok { + input.DeliveryChannel.ConfigSnapshotDeliveryProperties = &types.ConfigSnapshotDeliveryProperties{ + DeliveryFrequency: types.MaximumExecutionFrequency(v.(string)), } } } - input := configservice.PutDeliveryChannelInput{DeliveryChannel: &channel} - - err := retry.RetryContext(ctx, propagationTimeout, func() *retry.RetryError { - _, err := conn.PutDeliveryChannelWithContext(ctx, &input) - if err == nil { - return nil - } - - if tfawserr.ErrCodeEquals(err, "InsufficientDeliveryPolicyException") { - return retry.RetryableError(err) - } + if v, ok := d.GetOk("sns_topic_arn"); ok { + input.DeliveryChannel.SnsTopicARN = aws.String(v.(string)) + } - return retry.NonRetryableError(err) + _, err := tfresource.RetryWhenIsA[*types.InsufficientDeliveryPolicyException](ctx, propagationTimeout, func() (interface{}, error) { + return conn.PutDeliveryChannel(ctx, input) }) - if tfresource.TimedOut(err) { - _, err = conn.PutDeliveryChannelWithContext(ctx, &input) - } + if err != nil { - return sdkdiag.AppendErrorf(diags, "Creating Delivery Channel failed: %s", err) + return sdkdiag.AppendErrorf(diags, "putting ConfigService Delivery Channel (%s): %s", name, err) } - d.SetId(name) + if d.IsNewResource() { + d.SetId(name) + } return append(diags, resourceDeliveryChannelRead(ctx, d, meta)...) } func resourceDeliveryChannelRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - input := configservice.DescribeDeliveryChannelsInput{ - DeliveryChannelNames: []*string{aws.String(d.Id())}, - } + channel, err := findDeliveryChannelByName(ctx, conn, d.Id()) - out, err := conn.DescribeDeliveryChannelsWithContext(ctx, &input) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchDeliveryChannelException) { - create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameDeliveryChannel, d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Delivery Channel (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameDeliveryChannel, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading ConfigService Delivery Channel (%s): %s", d.Id(), err) } - if !d.IsNewResource() && len(out.DeliveryChannels) < 1 { - create.LogNotFoundRemoveState(names.ConfigService, create.ErrActionReading, ResNameDeliveryChannel, d.Id()) - d.SetId("") - return diags - } - - if d.IsNewResource() && len(out.DeliveryChannels) < 1 { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameDeliveryChannel, d.Id(), errors.New("not found after creation")) - } - - if len(out.DeliveryChannels) > 1 { - return sdkdiag.AppendErrorf(diags, "Received %d delivery channels under %s (expected exactly 1): %s", - len(out.DeliveryChannels), d.Id(), out.DeliveryChannels) - } - - channel := out.DeliveryChannels[0] - d.Set("name", channel.Name) d.Set("s3_bucket_name", channel.S3BucketName) d.Set("s3_key_prefix", channel.S3KeyPrefix) d.Set("s3_kms_key_arn", channel.S3KmsKeyArn) - d.Set("sns_topic_arn", channel.SnsTopicARN) - if channel.ConfigSnapshotDeliveryProperties != nil { d.Set("snapshot_delivery_properties", flattenSnapshotDeliveryProperties(channel.ConfigSnapshotDeliveryProperties)) } + d.Set("sns_topic_arn", channel.SnsTopicARN) return diags } func resourceDeliveryChannelDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) - input := configservice.DeleteDeliveryChannelInput{ - DeliveryChannelName: aws.String(d.Id()), + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + const ( + timeout = 30 * time.Second + ) + log.Printf("[DEBUG] Deleting ConfigService Delivery Channel: %s", d.Id()) + _, err := tfresource.RetryWhenIsAErrorMessageContains[*types.LastDeliveryChannelDeleteFailedException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteDeliveryChannel(ctx, &configservice.DeleteDeliveryChannelInput{ + DeliveryChannelName: aws.String(d.Id()), + }) + }, "there is a running configuration recorder") + + if errs.IsA[*types.NoSuchDeliveryChannelException](err) { + return diags } - err := retry.RetryContext(ctx, 30*time.Second, func() *retry.RetryError { - _, err := conn.DeleteDeliveryChannelWithContext(ctx, &input) - if err != nil { - if tfawserr.ErrMessageContains(err, configservice.ErrCodeLastDeliveryChannelDeleteFailedException, "there is a running configuration recorder") { - return retry.RetryableError(err) - } + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Delivery Channel (%s): %s", d.Id(), err) + } + + return diags +} + +func findDeliveryChannelByName(ctx context.Context, conn *configservice.Client, name string) (*types.DeliveryChannel, error) { + input := &configservice.DescribeDeliveryChannelsInput{ + DeliveryChannelNames: []string{name}, + } + + return findDeliveryChannel(ctx, conn, input) +} + +func findDeliveryChannel(ctx context.Context, conn *configservice.Client, input *configservice.DescribeDeliveryChannelsInput) (*types.DeliveryChannel, error) { + output, err := findDeliveryChannels(ctx, conn, input) + + if err != nil { + return nil, err + } - return retry.NonRetryableError(err) + return tfresource.AssertSingleValueResult(output) +} + +func findDeliveryChannels(ctx context.Context, conn *configservice.Client, input *configservice.DescribeDeliveryChannelsInput) ([]types.DeliveryChannel, error) { + output, err := conn.DescribeDeliveryChannels(ctx, input) + + if errs.IsA[*types.NoSuchDeliveryChannelException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, } - return nil - }) - if tfresource.TimedOut(err) { - _, err = conn.DeleteDeliveryChannelWithContext(ctx, &input) } + if err != nil { - return sdkdiag.AppendErrorf(diags, "Unable to delete delivery channel: %s", err) + return nil, err } - return diags + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.DeliveryChannels, nil +} + +func flattenSnapshotDeliveryProperties(apiObject *types.ConfigSnapshotDeliveryProperties) []interface{} { + if apiObject == nil { + return nil + } + + tfMap := map[string]interface{}{ + "delivery_frequency": apiObject.DeliveryFrequency, + } + + return []interface{}{tfMap} } diff --git a/internal/service/configservice/delivery_channel_test.go b/internal/service/configservice/delivery_channel_test.go index 058abae0e40..bb2db30df82 100644 --- a/internal/service/configservice/delivery_channel_test.go +++ b/internal/service/configservice/delivery_channel_test.go @@ -8,22 +8,22 @@ import ( "fmt" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccDeliveryChannel_basic(t *testing.T) { ctx := acctest.Context(t) - var dc configservice.DeliveryChannel - rInt := sdkacctest.RandInt() - expectedName := fmt.Sprintf("tf-acc-test-awsconfig-%d", rInt) - expectedBucketName := fmt.Sprintf("tf-acc-test-awsconfig-%d", rInt) + var dc types.DeliveryChannel + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_config_delivery_channel.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -32,25 +32,27 @@ func testAccDeliveryChannel_basic(t *testing.T) { CheckDestroy: testAccCheckDeliveryChannelDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDeliveryChannelConfig_basic(rInt), + Config: testAccDeliveryChannelConfig_basic(rName), Check: resource.ComposeTestCheckFunc( - testAccCheckDeliveryChannelExists(ctx, "aws_config_delivery_channel.foo", &dc), - testAccCheckDeliveryChannelName("aws_config_delivery_channel.foo", expectedName, &dc), - resource.TestCheckResourceAttr("aws_config_delivery_channel.foo", "name", expectedName), - resource.TestCheckResourceAttr("aws_config_delivery_channel.foo", "s3_bucket_name", expectedBucketName), + testAccCheckDeliveryChannelExists(ctx, resourceName, &dc), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "s3_bucket_name", rName), ), }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, }, }) } -func testAccDeliveryChannel_allParams(t *testing.T) { +func testAccDeliveryChannel_disappears(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_config_delivery_channel.foo" - var dc configservice.DeliveryChannel - rInt := sdkacctest.RandInt() - expectedName := fmt.Sprintf("tf-acc-test-awsconfig-%d", rInt) - expectedBucketName := fmt.Sprintf("tf-acc-test-awsconfig-%d", rInt) + var dc types.DeliveryChannel + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_config_delivery_channel.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -59,26 +61,22 @@ func testAccDeliveryChannel_allParams(t *testing.T) { CheckDestroy: testAccCheckDeliveryChannelDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDeliveryChannelConfig_allParams(rInt), + Config: testAccDeliveryChannelConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckDeliveryChannelExists(ctx, resourceName, &dc), - testAccCheckDeliveryChannelName(resourceName, expectedName, &dc), - resource.TestCheckResourceAttr(resourceName, "name", expectedName), - resource.TestCheckResourceAttr(resourceName, "s3_bucket_name", expectedBucketName), - resource.TestCheckResourceAttr(resourceName, "s3_key_prefix", "one/two/three"), - resource.TestCheckResourceAttrPair(resourceName, "s3_kms_key_arn", "aws_kms_key.k", "arn"), - resource.TestCheckResourceAttrPair(resourceName, "sns_topic_arn", "aws_sns_topic.t", "arn"), - resource.TestCheckResourceAttr(resourceName, "snapshot_delivery_properties.0.delivery_frequency", "Six_Hours"), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfconfig.ResourceDeliveryChannel(), resourceName), ), + ExpectNonEmptyPlan: true, }, }, }) } -func testAccDeliveryChannel_importBasic(t *testing.T) { +func testAccDeliveryChannel_allParams(t *testing.T) { ctx := acctest.Context(t) - resourceName := "aws_config_delivery_channel.foo" - rInt := sdkacctest.RandInt() + var dc types.DeliveryChannel + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) + resourceName := "aws_config_delivery_channel.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -87,9 +85,17 @@ func testAccDeliveryChannel_importBasic(t *testing.T) { CheckDestroy: testAccCheckDeliveryChannelDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccDeliveryChannelConfig_basic(rInt), + Config: testAccDeliveryChannelConfig_allParams(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckDeliveryChannelExists(ctx, resourceName, &dc), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "s3_bucket_name", rName), + resource.TestCheckResourceAttr(resourceName, "s3_key_prefix", "one/two/three"), + resource.TestCheckResourceAttrPair(resourceName, "s3_kms_key_arn", "aws_kms_key.test", "arn"), + resource.TestCheckResourceAttrPair(resourceName, "sns_topic_arn", "aws_sns_topic.test", "arn"), + resource.TestCheckResourceAttr(resourceName, "snapshot_delivery_properties.0.delivery_frequency", "Six_Hours"), + ), }, - { ResourceName: resourceName, ImportState: true, @@ -99,43 +105,22 @@ func testAccDeliveryChannel_importBasic(t *testing.T) { }) } -func testAccCheckDeliveryChannelName(n, desired string, obj *configservice.DeliveryChannel) resource.TestCheckFunc { +func testAccCheckDeliveryChannelExists(ctx context.Context, n string, v *types.DeliveryChannel) resource.TestCheckFunc { return func(s *terraform.State) error { - _, ok := s.RootModule().Resources[n] + rs, ok := s.RootModule().Resources[n] if !ok { return fmt.Errorf("Not found: %s", n) } - if *obj.Name != desired { - return fmt.Errorf("Expected name: %q, given: %q", desired, *obj.Name) - } - return nil - } -} -func testAccCheckDeliveryChannelExists(ctx context.Context, n string, obj *configservice.DeliveryChannel) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not Found: %s", n) - } + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - if rs.Primary.ID == "" { - return fmt.Errorf("No delivery channel ID is set") - } + output, err := tfconfig.FindDeliveryChannelByName(ctx, conn, rs.Primary.ID) - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) - out, err := conn.DescribeDeliveryChannelsWithContext(ctx, &configservice.DescribeDeliveryChannelsInput{ - DeliveryChannelNames: []*string{aws.String(rs.Primary.Attributes["name"])}, - }) if err != nil { - return fmt.Errorf("Failed to describe delivery channel: %s", err) - } - if len(out.DeliveryChannels) < 1 { - return fmt.Errorf("No delivery channel found when describing %q", rs.Primary.Attributes["name"]) + return err } - dc := out.DeliveryChannels[0] - *obj = *dc + *v = *output return nil } @@ -143,38 +128,39 @@ func testAccCheckDeliveryChannelExists(ctx context.Context, n string, obj *confi func testAccCheckDeliveryChannelDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_delivery_channel" { continue } - resp, err := conn.DescribeDeliveryChannelsWithContext(ctx, &configservice.DescribeDeliveryChannelsInput{ - DeliveryChannelNames: []*string{aws.String(rs.Primary.Attributes["name"])}, - }) + _, err := tfconfig.FindDeliveryChannelByName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.DeliveryChannels) != 0 && - *resp.DeliveryChannels[0].Name == rs.Primary.Attributes["name"] { - return fmt.Errorf("Delivery Channel still exists: %s", rs.Primary.Attributes["name"]) - } + if tfresource.NotFound(err) { + continue + } + + if err != nil { + return err } + + return fmt.Errorf("ConfigService Delivery Channel %s still exists", rs.Primary.ID) } return nil } } -func testAccDeliveryChannelConfig_basic(randInt int) string { +func testAccDeliveryChannelConfig_base(rName string) string { return fmt.Sprintf(` -resource "aws_config_configuration_recorder" "foo" { - name = "tf-acc-test-%d" - role_arn = aws_iam_role.r.arn +resource "aws_config_configuration_recorder" "test" { + name = %[1]q + role_arn = aws_iam_role.test.arn } -resource "aws_iam_role" "r" { - name = "tf-acc-test-awsconfig-%d" +resource "aws_iam_role" "test" { + name = %[1]q assume_role_policy = < 0 { - source.AccountIds = flex.ExpandStringList(accountIDs) - } - } - - if v, ok := detail["regions"]; ok { - regions := v.([]interface{}) - if len(regions) > 0 { - source.AwsRegions = flex.ExpandStringList(regions) - } - } - - results = append(results, &source) - } - return results -} - -func expandOrganizationAggregationSource(configured map[string]interface{}) *configservice.OrganizationAggregationSource { - source := configservice.OrganizationAggregationSource{ - AllAwsRegions: aws.Bool(configured["all_regions"].(bool)), - RoleArn: aws.String(configured["role_arn"].(string)), - } - - if v, ok := configured["regions"]; ok { - regions := v.([]interface{}) - if len(regions) > 0 { - source.AwsRegions = flex.ExpandStringList(regions) - } - } - - return &source -} - -func expandRecordingGroup(group map[string]interface{}) *configservice.RecordingGroup { - recordingGroup := configservice.RecordingGroup{} - - if v, ok := group["all_supported"]; ok { - recordingGroup.AllSupported = aws.Bool(v.(bool)) - } - - if v, ok := group["exclusion_by_resource_types"]; ok { - if len(v.([]interface{})) > 0 { - recordingGroup.ExclusionByResourceTypes = expandRecordingGroupExclusionByResourceTypes(v.([]interface{})) - } - } - - if v, ok := group["include_global_resource_types"]; ok { - recordingGroup.IncludeGlobalResourceTypes = aws.Bool(v.(bool)) - } - - if v, ok := group["recording_strategy"]; ok { - if len(v.([]interface{})) > 0 { - recordingGroup.RecordingStrategy = expandRecordingGroupRecordingStrategy(v.([]interface{})) - } - } - - if v, ok := group["resource_types"]; ok { - recordingGroup.ResourceTypes = flex.ExpandStringSet(v.(*schema.Set)) - } - return &recordingGroup -} - -func expandRecordingGroupExclusionByResourceTypes(configured []interface{}) *configservice.ExclusionByResourceTypes { - exclusionByResourceTypes := configservice.ExclusionByResourceTypes{} - exclusion := configured[0].(map[string]interface{}) - if v, ok := exclusion["resource_types"]; ok { - exclusionByResourceTypes.ResourceTypes = flex.ExpandStringSet(v.(*schema.Set)) - } - return &exclusionByResourceTypes -} - -func expandRecordingGroupRecordingStrategy(configured []interface{}) *configservice.RecordingStrategy { - recordingStrategy := configservice.RecordingStrategy{} - strategy := configured[0].(map[string]interface{}) - if v, ok := strategy["use_only"].(string); ok { - recordingStrategy.UseOnly = aws.String(v) - } - return &recordingStrategy -} - -func expandRecordingMode(mode map[string]interface{}) *configservice.RecordingMode { - recordingMode := configservice.RecordingMode{} - - if v, ok := mode["recording_frequency"].(string); ok { - recordingMode.RecordingFrequency = aws.String(v) - } - - if v, ok := mode["recording_mode_override"]; ok { - recordingMode.RecordingModeOverrides = expandRecordingModeRecordingModeOverrides(v.([]interface{})) - } - - return &recordingMode -} - -func expandRecordingModeRecordingModeOverrides(configured []interface{}) []*configservice.RecordingModeOverride { - var out []*configservice.RecordingModeOverride - for _, val := range configured { - m, ok := val.(map[string]interface{}) - if !ok { - continue - } - - e := &configservice.RecordingModeOverride{} - - if v, ok := m["description"].(string); ok && v != "" { - e.Description = aws.String(v) - } - - if v, ok := m["resource_types"]; ok { - e.ResourceTypes = flex.ExpandStringSet(v.(*schema.Set)) - } - - if v, ok := m["recording_frequency"].(string); ok && v != "" { - e.RecordingFrequency = aws.String(v) - } - - out = append(out, e) - } - return out -} - -func expandRulesEvaluationModes(in []interface{}) []*configservice.EvaluationModeConfiguration { - if len(in) == 0 { - return nil - } - - var out []*configservice.EvaluationModeConfiguration - for _, val := range in { - m, ok := val.(map[string]interface{}) - if !ok { - continue - } - - e := &configservice.EvaluationModeConfiguration{} - if v, ok := m["mode"].(string); ok && v != "" { - e.Mode = aws.String(v) - } - - out = append(out, e) - } - - return out -} - -func expandRuleScope(l []interface{}) *configservice.Scope { - if len(l) == 0 || l[0] == nil { - return nil - } - configured := l[0].(map[string]interface{}) - scope := &configservice.Scope{} - - if v, ok := configured["compliance_resource_id"].(string); ok && v != "" { - scope.ComplianceResourceId = aws.String(v) - } - if v, ok := configured["compliance_resource_types"]; ok { - l := v.(*schema.Set) - if l.Len() > 0 { - scope.ComplianceResourceTypes = flex.ExpandStringSet(l) - } - } - if v, ok := configured["tag_key"].(string); ok && v != "" { - scope.TagKey = aws.String(v) - } - if v, ok := configured["tag_value"].(string); ok && v != "" { - scope.TagValue = aws.String(v) - } - - return scope -} - -func expandRuleSource(configured []interface{}) *configservice.Source { - cfg := configured[0].(map[string]interface{}) - source := configservice.Source{ - Owner: aws.String(cfg["owner"].(string)), - } - - if v, ok := cfg["source_identifier"].(string); ok && v != "" { - source.SourceIdentifier = aws.String(v) - } - - if details, ok := cfg["source_detail"]; ok { - source.SourceDetails = expandRuleSourceDetails(details.(*schema.Set)) - } - - if v, ok := cfg["custom_policy_details"].([]interface{}); ok && len(v) > 0 { - source.CustomPolicyDetails = expandRuleSourceCustomPolicyDetails(v) - } - - return &source -} - -func expandRuleSourceDetails(configured *schema.Set) []*configservice.SourceDetail { - var results []*configservice.SourceDetail - - for _, item := range configured.List() { - detail := item.(map[string]interface{}) - src := configservice.SourceDetail{} - - if msgType, ok := detail["message_type"].(string); ok && msgType != "" { - src.MessageType = aws.String(msgType) - } - if eventSource, ok := detail["event_source"].(string); ok && eventSource != "" { - src.EventSource = aws.String(eventSource) - } - if maxExecFreq, ok := detail["maximum_execution_frequency"].(string); ok && maxExecFreq != "" { - src.MaximumExecutionFrequency = aws.String(maxExecFreq) - } - - results = append(results, &src) - } - - return results -} - -func expandRuleSourceCustomPolicyDetails(configured []interface{}) *configservice.CustomPolicyDetails { - cfg := configured[0].(map[string]interface{}) - source := configservice.CustomPolicyDetails{ - PolicyRuntime: aws.String(cfg["policy_runtime"].(string)), - PolicyText: aws.String(cfg["policy_text"].(string)), - EnableDebugLogDelivery: aws.Bool(cfg["enable_debug_log_delivery"].(bool)), - } - - return &source -} - -func flattenAccountAggregationSources(sources []*configservice.AccountAggregationSource) []interface{} { - var result []interface{} - - if len(sources) == 0 { - return result - } - - source := sources[0] - m := make(map[string]interface{}) - m["account_ids"] = flex.FlattenStringList(source.AccountIds) - m["all_regions"] = aws.BoolValue(source.AllAwsRegions) - m["regions"] = flex.FlattenStringList(source.AwsRegions) - result = append(result, m) - return result -} - -func flattenOrganizationAggregationSource(source *configservice.OrganizationAggregationSource) []interface{} { - var result []interface{} - - if source == nil { - return result - } - - m := make(map[string]interface{}) - m["all_regions"] = aws.BoolValue(source.AllAwsRegions) - m["regions"] = flex.FlattenStringList(source.AwsRegions) - m["role_arn"] = aws.StringValue(source.RoleArn) - result = append(result, m) - return result -} - -func flattenRecordingGroup(g *configservice.RecordingGroup) []map[string]interface{} { - m := make(map[string]interface{}, 1) - - if g.AllSupported != nil { - m["all_supported"] = aws.BoolValue(g.AllSupported) - } - - if g.ExclusionByResourceTypes != nil { - m["exclusion_by_resource_types"] = flattenExclusionByResourceTypes(g.ExclusionByResourceTypes) - } - - if g.IncludeGlobalResourceTypes != nil { - m["include_global_resource_types"] = aws.BoolValue(g.IncludeGlobalResourceTypes) - } - - if g.RecordingStrategy != nil { - m["recording_strategy"] = flattenRecordingGroupRecordingStrategy(g.RecordingStrategy) - } - - if g.ResourceTypes != nil && len(g.ResourceTypes) > 0 { - m["resource_types"] = flex.FlattenStringSet(g.ResourceTypes) - } - - return []map[string]interface{}{m} -} - -func flattenRecordingMode(g *configservice.RecordingMode) []map[string]interface{} { - m := make(map[string]interface{}, 1) - - if g.RecordingFrequency != nil { - m["recording_frequency"] = aws.StringValue(g.RecordingFrequency) - } - - if g.RecordingModeOverrides != nil && len(g.RecordingModeOverrides) > 0 { - m["recording_mode_override"] = flattenRecordingModeRecordingModeOverrides(g.RecordingModeOverrides) - } - - return []map[string]interface{}{m} -} - -func flattenRecordingModeRecordingModeOverrides(in []*configservice.RecordingModeOverride) []interface{} { - var out []interface{} - for _, v := range in { - m := map[string]interface{}{ - "description": aws.StringValue(v.Description), - "recording_frequency": aws.StringValue(v.RecordingFrequency), - } - - if v.ResourceTypes != nil { - m["resource_types"] = flex.FlattenStringSet(v.ResourceTypes) - } - - out = append(out, m) - } - - return out -} - -func flattenExclusionByResourceTypes(exclusionByResourceTypes *configservice.ExclusionByResourceTypes) []interface{} { - if exclusionByResourceTypes == nil { - return nil - } - m := make(map[string]interface{}) - if exclusionByResourceTypes.ResourceTypes != nil { - m["resource_types"] = flex.FlattenStringSet(exclusionByResourceTypes.ResourceTypes) - } - - return []interface{}{m} -} - -func flattenRuleEvaluationMode(in []*configservice.EvaluationModeConfiguration) []interface{} { - if len(in) == 0 { - return nil - } - - var out []interface{} - for _, v := range in { - m := map[string]interface{}{ - "mode": aws.StringValue(v.Mode), - } - - out = append(out, m) - } - - return out -} - -func flattenRecordingGroupRecordingStrategy(recordingStrategy *configservice.RecordingStrategy) []interface{} { - if recordingStrategy == nil { - return nil - } - m := make(map[string]interface{}) - if recordingStrategy.UseOnly != nil { - m["use_only"] = aws.StringValue(recordingStrategy.UseOnly) - } - - return []interface{}{m} -} - -func flattenRuleScope(scope *configservice.Scope) []interface{} { - var items []interface{} - - m := make(map[string]interface{}) - if scope.ComplianceResourceId != nil { - m["compliance_resource_id"] = aws.StringValue(scope.ComplianceResourceId) - } - if scope.ComplianceResourceTypes != nil { - m["compliance_resource_types"] = flex.FlattenStringSet(scope.ComplianceResourceTypes) - } - if scope.TagKey != nil { - m["tag_key"] = aws.StringValue(scope.TagKey) - } - if scope.TagValue != nil { - m["tag_value"] = aws.StringValue(scope.TagValue) - } - - items = append(items, m) - return items -} - -func flattenRuleSource(source *configservice.Source) []interface{} { - var result []interface{} - m := make(map[string]interface{}) - m["owner"] = aws.StringValue(source.Owner) - m["source_identifier"] = aws.StringValue(source.SourceIdentifier) - - if source.CustomPolicyDetails != nil { - m["custom_policy_details"] = flattenRuleSourceCustomPolicyDetails(source.CustomPolicyDetails) - } - - if len(source.SourceDetails) > 0 { - m["source_detail"] = flattenRuleSourceDetails(source.SourceDetails) - } - - result = append(result, m) - return result -} - -func flattenRuleSourceCustomPolicyDetails(source *configservice.CustomPolicyDetails) []interface{} { - var result []interface{} - m := make(map[string]interface{}) - m["policy_runtime"] = aws.StringValue(source.PolicyRuntime) - m["policy_text"] = aws.StringValue(source.PolicyText) - m["enable_debug_log_delivery"] = aws.BoolValue(source.EnableDebugLogDelivery) - - result = append(result, m) - return result -} - -func flattenRuleSourceDetails(details []*configservice.SourceDetail) []interface{} { - var items []interface{} - for _, d := range details { - m := make(map[string]interface{}) - if d.MessageType != nil { - m["message_type"] = aws.StringValue(d.MessageType) - } - if d.EventSource != nil { - m["event_source"] = aws.StringValue(d.EventSource) - } - if d.MaximumExecutionFrequency != nil { - m["maximum_execution_frequency"] = aws.StringValue(d.MaximumExecutionFrequency) - } - - items = append(items, m) - } - - return items -} - -func flattenSnapshotDeliveryProperties(p *configservice.ConfigSnapshotDeliveryProperties) []map[string]interface{} { - m := make(map[string]interface{}) - - if p.DeliveryFrequency != nil { - m["delivery_frequency"] = aws.StringValue(p.DeliveryFrequency) - } - - return []map[string]interface{}{m} -} diff --git a/internal/service/configservice/generate.go b/internal/service/configservice/generate.go index 74dc65f9061..0f0e1f2c966 100644 --- a/internal/service/configservice/generate.go +++ b/internal/service/configservice/generate.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -//go:generate go run ../../generate/tags/main.go -ListTags -ServiceTagsSlice -UpdateTags +//go:generate go run ../../generate/tags/main.go -AWSSDKVersion=2 -ListTags -ServiceTagsSlice -UpdateTags //go:generate go run ../../generate/servicepackage/main.go // ONLY generate directives and package declaration! Do not add anything else to this file. diff --git a/internal/service/configservice/organization_conformance_pack.go b/internal/service/configservice/organization_conformance_pack.go index 626708afa31..4e4e6a4f8e0 100644 --- a/internal/service/configservice/organization_conformance_pack.go +++ b/internal/service/configservice/organization_conformance_pack.go @@ -5,24 +5,30 @@ package configservice import ( "context" + "errors" + "fmt" "log" "time" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "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" ) -// @SDKResource("aws_config_organization_conformance_pack") -func ResourceOrganizationConformancePack() *schema.Resource { +// @SDKResource("aws_config_organization_conformance_pack", name="Organization Conformance Pack") +func resourceOrganizationConformancePack() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceOrganizationConformancePackCreate, ReadWithoutTimeout: resourceOrganizationConformancePackRead, @@ -35,8 +41,8 @@ func ResourceOrganizationConformancePack() *schema.Resource { Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(10 * time.Minute), - Delete: schema.DefaultTimeout(20 * time.Minute), Update: schema.DefaultTimeout(10 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), }, Schema: map[string]*schema.Schema{ @@ -94,9 +100,10 @@ func ResourceOrganizationConformancePack() *schema.Resource { ), }, "template_body": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: verify.SuppressEquivalentJSONOrYAMLDiffs, + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: verify.SuppressEquivalentJSONOrYAMLDiffs, + DiffSuppressOnRefresh: true, ValidateFunc: validation.All( validation.StringLenBetween(1, 51200), verify.ValidStringIsJSONOrYAML, @@ -118,10 +125,9 @@ func ResourceOrganizationConformancePack() *schema.Resource { func resourceOrganizationConformancePackCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) name := d.Get("name").(string) - input := &configservice.PutOrganizationConformancePackInput{ OrganizationConformancePackName: aws.String(name), } @@ -134,12 +140,12 @@ func resourceOrganizationConformancePackCreate(ctx context.Context, d *schema.Re input.DeliveryS3KeyPrefix = aws.String(v.(string)) } - if v, ok := d.GetOk("excluded_accounts"); ok { - input.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("excluded_accounts"); ok && v.(*schema.Set).Len() > 0 { + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameter"); ok { - input.ConformancePackInputParameters = expandConfigConformancePackInputParameters(v.(*schema.Set).List()) + input.ConformancePackInputParameters = expandConformancePackInputParameters(v.(*schema.Set).List()) } if v, ok := d.GetOk("template_body"); ok { @@ -150,16 +156,18 @@ func resourceOrganizationConformancePackCreate(ctx context.Context, d *schema.Re input.TemplateS3Uri = aws.String(v.(string)) } - _, err := conn.PutOrganizationConformancePackWithContext(ctx, input) + _, err := tfresource.RetryWhenIsA[*types.OrganizationAccessDeniedException](ctx, organizationsPropagationTimeout, func() (interface{}, error) { + return conn.PutOrganizationConformancePack(ctx, input) + }) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Config Organization Conformance Pack (%s): %s", name, err) + return sdkdiag.AppendErrorf(diags, "creating ConfigService Organization Conformance Pack (%s): %s", name, err) } d.SetId(name) - if err := waitForOrganizationConformancePackStatusCreateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Config Organization Conformance Pack (%s) to be created: %s", d.Id(), err) + if _, err := waitOrganizationConformancePackCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Conformance Pack (%s) create: %s", d.Id(), err) } return append(diags, resourceOrganizationConformancePackRead(ctx, d, meta)...) @@ -167,49 +175,35 @@ func resourceOrganizationConformancePackCreate(ctx context.Context, d *schema.Re func resourceOrganizationConformancePackRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - pack, err := DescribeOrganizationConformancePack(ctx, conn, d.Id()) + pack, err := findOrganizationConformancePackByName(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConformancePackException) { - log.Printf("[WARN] Config Organization Conformance Pack (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Organization Conformance Pack (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "describing Config Organization Conformance Pack (%s): %s", d.Id(), err) - } - - if pack == nil { - if d.IsNewResource() { - return sdkdiag.AppendErrorf(diags, "describing Config Organization Conformance Pack (%s): not found", d.Id()) - } - - log.Printf("[WARN] Config Organization Conformance Pack (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags + return sdkdiag.AppendErrorf(diags, "reading ConfigService Organization Conformance Pack (%s): %s", d.Id(), err) } d.Set("arn", pack.OrganizationConformancePackArn) - d.Set("name", pack.OrganizationConformancePackName) d.Set("delivery_s3_bucket", pack.DeliveryS3Bucket) d.Set("delivery_s3_key_prefix", pack.DeliveryS3KeyPrefix) - - if err = d.Set("excluded_accounts", flex.FlattenStringSet(pack.ExcludedAccounts)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting excluded_accounts: %s", err) - } - - if err = d.Set("input_parameter", flattenConfigConformancePackInputParameters(pack.ConformancePackInputParameters)); err != nil { + d.Set("excluded_accounts", pack.ExcludedAccounts) + if err = d.Set("input_parameter", flattenConformancePackInputParameters(pack.ConformancePackInputParameters)); err != nil { return sdkdiag.AppendErrorf(diags, "setting input_parameter: %s", err) } + d.Set("name", pack.OrganizationConformancePackName) return diags } func resourceOrganizationConformancePackUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) input := &configservice.PutOrganizationConformancePackInput{ OrganizationConformancePackName: aws.String(d.Id()), @@ -224,11 +218,11 @@ func resourceOrganizationConformancePackUpdate(ctx context.Context, d *schema.Re } if v, ok := d.GetOk("excluded_accounts"); ok { - input.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameter"); ok { - input.ConformancePackInputParameters = expandConfigConformancePackInputParameters(v.(*schema.Set).List()) + input.ConformancePackInputParameters = expandConformancePackInputParameters(v.(*schema.Set).List()) } if v, ok := d.GetOk("template_body"); ok { @@ -239,14 +233,14 @@ func resourceOrganizationConformancePackUpdate(ctx context.Context, d *schema.Re input.TemplateS3Uri = aws.String(v.(string)) } - _, err := conn.PutOrganizationConformancePackWithContext(ctx, input) + _, err := conn.PutOrganizationConformancePack(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "updating Config Organization Conformance Pack (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating ConfigService Organization Conformance Pack (%s): %s", d.Id(), err) } - if err := waitForOrganizationConformancePackStatusUpdateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Config Organization Conformance Pack (%s) to be updated: %s", d.Id(), err) + if _, err := waitOrganizationConformancePackUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Conformance Pack (%s) update: %s", d.Id(), err) } return append(diags, resourceOrganizationConformancePackRead(ctx, d, meta)...) @@ -254,28 +248,283 @@ func resourceOrganizationConformancePackUpdate(ctx context.Context, d *schema.Re func resourceOrganizationConformancePackDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + const ( + timeout = 5 * time.Minute + ) + log.Printf("[DEBUG] Deleting ConfigService Organization Conformance Pack: %s", d.Id()) + _, err := tfresource.RetryWhenIsA[*types.ResourceInUseException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteOrganizationConformancePack(ctx, &configservice.DeleteOrganizationConformancePackInput{ + OrganizationConformancePackName: aws.String(d.Id()), + }) + }) + + if errs.IsA[*types.NoSuchOrganizationConformancePackException](err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { + return diags + } - input := &configservice.DeleteOrganizationConformancePackInput{ - OrganizationConformancePackName: aws.String(d.Id()), + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Organization Conformance Pack (%s): %s", d.Id(), err) } - _, err := conn.DeleteOrganizationConformancePackWithContext(ctx, input) + if _, err := waitOrganizationConformancePackDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Conformance Pack (%s) delete: %s", d.Id(), err) + } - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConformancePackException) { - return diags + return diags +} + +func findOrganizationConformancePackByName(ctx context.Context, conn *configservice.Client, name string) (*types.OrganizationConformancePack, error) { + input := &configservice.DescribeOrganizationConformancePacksInput{ + OrganizationConformancePackNames: []string{name}, + } + + return findOrganizationConformancePack(ctx, conn, input) +} + +func findOrganizationConformancePack(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConformancePacksInput) (*types.OrganizationConformancePack, error) { + output, err := findOrganizationConformancePacks(ctx, conn, input) + + if err != nil { + return nil, err } + return tfresource.AssertSingleValueResult(output) +} + +func findOrganizationConformancePacks(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConformancePacksInput) ([]types.OrganizationConformancePack, error) { + var output []types.OrganizationConformancePack + + pages := configservice.NewDescribeOrganizationConformancePacksPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchOrganizationConformancePackException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if errs.IsAErrorMessageContains[*types.OrganizationAccessDeniedException](err, "This action can only be made by accounts in an AWS Organization") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.OrganizationConformancePacks...) + } + + return output, nil +} + +func findOrganizationConformancePackStatusByName(ctx context.Context, conn *configservice.Client, name string) (*types.OrganizationConformancePackStatus, error) { + input := &configservice.DescribeOrganizationConformancePackStatusesInput{ + OrganizationConformancePackNames: []string{name}, + } + + output, err := findOrganizationConformancePackStatus(ctx, conn, input) + if err != nil { - return sdkdiag.AppendErrorf(diags, "erorr deleting Config Organization Conformance Pack (%s): %s", d.Id(), err) + return nil, err } - if err := waitForOrganizationConformancePackStatusDeleteSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConformancePackException) { - return diags + if status := output.Status; status == types.OrganizationResourceStatusDeleteSuccessful { + return nil, &retry.NotFoundError{ + Message: string(status), + LastRequest: input, } - return sdkdiag.AppendErrorf(diags, "waiting for Config Organization Conformance Pack (%s) to be deleted: %s", d.Id(), err) } - return diags + return output, nil +} + +func findOrganizationConformancePackStatus(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConformancePackStatusesInput) (*types.OrganizationConformancePackStatus, error) { + output, err := findOrganizationConformancePackStatuses(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findOrganizationConformancePackStatuses(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConformancePackStatusesInput) ([]types.OrganizationConformancePackStatus, error) { + var output []types.OrganizationConformancePackStatus + + pages := configservice.NewDescribeOrganizationConformancePackStatusesPaginator(conn, input) + for pages.HasMorePages() { + const ( + timeout = 15 * time.Second + ) + outputRaw, err := tfresource.RetryWhenIsA[*types.OrganizationAccessDeniedException](ctx, timeout, func() (interface{}, error) { + return pages.NextPage(ctx) + }) + + if errs.IsA[*types.NoSuchOrganizationConformancePackException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, outputRaw.(*configservice.DescribeOrganizationConformancePackStatusesOutput).OrganizationConformancePackStatuses...) + } + + return output, nil +} + +func findOrganizationConformancePackDetailedStatusesByTwoPartKey(ctx context.Context, conn *configservice.Client, name string, status types.OrganizationResourceDetailedStatus) ([]types.OrganizationConformancePackDetailedStatus, error) { + input := &configservice.GetOrganizationConformancePackDetailedStatusInput{ + Filters: &types.OrganizationResourceDetailedStatusFilters{ + Status: status, + }, + OrganizationConformancePackName: aws.String(name), + } + + return findOrganizationConformancePackDetailedStatuses(ctx, conn, input) +} + +func findOrganizationConformancePackDetailedStatuses(ctx context.Context, conn *configservice.Client, input *configservice.GetOrganizationConformancePackDetailedStatusInput) ([]types.OrganizationConformancePackDetailedStatus, error) { + var output []types.OrganizationConformancePackDetailedStatus + + pages := configservice.NewGetOrganizationConformancePackDetailedStatusPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchOrganizationConformancePackException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if errs.IsAErrorMessageContains[*types.OrganizationAccessDeniedException](err, "This action can only be made by accounts in an AWS Organization") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.OrganizationConformancePackDetailedStatuses...) + } + + return output, nil +} + +func statusOrganizationConformancePack(ctx context.Context, conn *configservice.Client, name string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findOrganizationConformancePackStatusByName(ctx, conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.Status), err + } +} + +func waitOrganizationConformancePackCreated(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.OrganizationConformancePackStatus, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.OrganizationResourceStatusCreateInProgress), + Target: enum.Slice(types.OrganizationResourceStatusCreateSuccessful), + Refresh: statusOrganizationConformancePack(ctx, conn, name), + Timeout: timeout, + Delay: 30 * time.Second, + NotFoundChecks: 10, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.OrganizationConformancePackStatus); ok { + tfresource.SetLastError(err, organizationConformancePackStatusError(ctx, conn, output)) + + return output, err + } + + return nil, err +} + +func waitOrganizationConformancePackUpdated(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.OrganizationConformancePackStatus, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.OrganizationResourceStatusUpdateInProgress), + Target: enum.Slice(types.OrganizationResourceStatusUpdateSuccessful), + Refresh: statusOrganizationConformancePack(ctx, conn, name), + Timeout: timeout, + Delay: 10 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.OrganizationConformancePackStatus); ok { + tfresource.SetLastError(err, organizationConformancePackStatusError(ctx, conn, output)) + + return output, err + } + + return nil, err +} + +func waitOrganizationConformancePackDeleted(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.OrganizationConformancePackStatus, error) { + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.OrganizationResourceStatusDeleteInProgress), + Target: []string{}, + Refresh: statusOrganizationConformancePack(ctx, conn, name), + Timeout: timeout, + Delay: 10 * time.Second, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.OrganizationConformancePackStatus); ok { + tfresource.SetLastError(err, organizationConformancePackStatusError(ctx, conn, output)) + + return output, err + } + + return nil, err +} + +func organizationConformancePackStatusError(ctx context.Context, conn *configservice.Client, apiObject *types.OrganizationConformancePackStatus) error { + errs := []error{fmt.Errorf("%s: %s", aws.ToString(apiObject.ErrorCode), aws.ToString(apiObject.ErrorMessage))} + + var detailedStatus types.OrganizationResourceDetailedStatus + switch apiObject.Status { + case types.OrganizationResourceStatusCreateFailed: + detailedStatus = types.OrganizationResourceDetailedStatusCreateFailed + case types.OrganizationResourceStatusUpdateFailed: + detailedStatus = types.OrganizationResourceDetailedStatusUpdateFailed + case types.OrganizationResourceStatusDeleteFailed: + detailedStatus = types.OrganizationResourceDetailedStatusDeleteFailed + } + + if detailedStatus != "" { + if v, err := findOrganizationConformancePackDetailedStatusesByTwoPartKey(ctx, conn, aws.ToString(apiObject.OrganizationConformancePackName), detailedStatus); err == nil { + for _, v := range v { + err := fmt.Errorf("%s: %s", aws.ToString(v.ErrorCode), aws.ToString(v.ErrorMessage)) + errs = append(errs, fmt.Errorf("Account ID (%s): %w", aws.ToString(v.AccountId), err)) + } + } + } + + return errors.Join(errs...) } diff --git a/internal/service/configservice/organization_conformance_pack_test.go b/internal/service/configservice/organization_conformance_pack_test.go index 83b67b65e3e..4b3fb66290d 100644 --- a/internal/service/configservice/organization_conformance_pack_test.go +++ b/internal/service/configservice/organization_conformance_pack_test.go @@ -5,25 +5,27 @@ package configservice_test import ( "context" + "errors" "fmt" "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccOrganizationConformancePack_basic(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_conformance_pack.test" @@ -57,7 +59,7 @@ func testAccOrganizationConformancePack_basic(t *testing.T) { func testAccOrganizationConformancePack_disappears(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_conformance_pack.test" @@ -81,7 +83,7 @@ func testAccOrganizationConformancePack_disappears(t *testing.T) { func testAccOrganizationConformancePack_excludedAccounts(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_conformance_pack.test" @@ -130,7 +132,7 @@ func testAccOrganizationConformancePack_excludedAccounts(t *testing.T) { func testAccOrganizationConformancePack_updateName(t *testing.T) { ctx := acctest.Context(t) - var before, after configservice.OrganizationConformancePack + var before, after types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) rNameUpdated := sdkacctest.RandomWithPrefix("tf-acc-test-update") resourceName := "aws_config_organization_conformance_pack.test" @@ -173,7 +175,7 @@ func testAccOrganizationConformancePack_updateName(t *testing.T) { func testAccOrganizationConformancePack_inputParameters(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) pKey := "ParamKey" pValue := "ParamValue" @@ -208,7 +210,7 @@ func testAccOrganizationConformancePack_inputParameters(t *testing.T) { func testAccOrganizationConformancePack_S3Delivery(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketName := sdkacctest.RandomWithPrefix("awsconfigconforms") resourceName := "aws_config_organization_conformance_pack.test" @@ -239,7 +241,7 @@ func testAccOrganizationConformancePack_S3Delivery(t *testing.T) { func testAccOrganizationConformancePack_S3Template(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_conformance_pack.test" @@ -273,7 +275,7 @@ func testAccOrganizationConformancePack_S3Template(t *testing.T) { func testAccOrganizationConformancePack_updateInputParameters(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_conformance_pack.test" @@ -323,7 +325,7 @@ func testAccOrganizationConformancePack_updateInputParameters(t *testing.T) { func testAccOrganizationConformancePack_updateS3Delivery(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketName := sdkacctest.RandomWithPrefix("awsconfigconforms") updatedBucketName := fmt.Sprintf("%s-update", bucketName) @@ -363,7 +365,7 @@ func testAccOrganizationConformancePack_updateS3Delivery(t *testing.T) { func testAccOrganizationConformancePack_updateS3Template(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) bucketName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_conformance_pack.test" @@ -398,7 +400,7 @@ func testAccOrganizationConformancePack_updateS3Template(t *testing.T) { func testAccOrganizationConformancePack_updateTemplateBody(t *testing.T) { ctx := acctest.Context(t) - var pack configservice.OrganizationConformancePack + var pack types.OrganizationConformancePack rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_conformance_pack.test" @@ -432,67 +434,55 @@ func testAccOrganizationConformancePack_updateTemplateBody(t *testing.T) { func testAccCheckOrganizationConformancePackDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_organization_conformance_pack" { continue } - pack, err := tfconfig.DescribeOrganizationConformancePack(ctx, conn, rs.Primary.ID) + _, err := tfconfig.FindOrganizationConformancePackByName(ctx, conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConformancePackException) { - continue - } - - // In the event the Organizations Organization is deleted first, its Conformance Packs - // are deleted and we can continue through the loop - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeOrganizationAccessDeniedException) { + if tfresource.NotFound(err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { continue } if err != nil { - return fmt.Errorf("error describing Config Organization Conformance Pack (%s): %w", rs.Primary.ID, err) + return err } - if pack != nil { - return fmt.Errorf("Config Organization Conformance Pack (%s) still exists", rs.Primary.ID) - } + return fmt.Errorf("ConfigService Organization Conformance Pack %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckOrganizationConformancePackExists(ctx context.Context, resourceName string, ocp *configservice.OrganizationConformancePack) resource.TestCheckFunc { +func testAccCheckOrganizationConformancePackExists(ctx context.Context, n string, v *types.OrganizationConformancePack) 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) } - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - pack, err := tfconfig.DescribeOrganizationConformancePack(ctx, conn, rs.Primary.ID) + output, err := tfconfig.FindOrganizationConformancePackByName(ctx, conn, rs.Primary.ID) if err != nil { - return fmt.Errorf("error describing Config Organization Conformance Pack (%s): %w", rs.Primary.ID, err) - } - - if pack == nil { - return fmt.Errorf("Config Organization Conformance Pack (%s) not found", rs.Primary.ID) + return err } - *ocp = *pack + *v = *output return nil } } -func testAccCheckOrganizationConformancePackRecreated(before, after *configservice.OrganizationConformancePack) resource.TestCheckFunc { +func testAccCheckOrganizationConformancePackRecreated(before, after *types.OrganizationConformancePack) resource.TestCheckFunc { return func(s *terraform.State) error { - if aws.StringValue(before.OrganizationConformancePackArn) == aws.StringValue(after.OrganizationConformancePackArn) { - return fmt.Errorf("AWS Config Organization Conformance Pack was not recreated") + if aws.ToString(before.OrganizationConformancePackArn) == aws.ToString(after.OrganizationConformancePackArn) { + return errors.New("ConfigService Organization Conformance Pack was not recreated") } return nil } diff --git a/internal/service/configservice/organization_custom_policy_rule.go b/internal/service/configservice/organization_custom_policy_rule.go index 86d97af5005..a2f738ff3fe 100644 --- a/internal/service/configservice/organization_custom_policy_rule.go +++ b/internal/service/configservice/organization_custom_policy_rule.go @@ -5,26 +5,27 @@ package configservice import ( "context" - "errors" "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "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" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_config_organization_custom_policy_rule") -func ResourceOrganizationCustomPolicyRule() *schema.Resource { +// @SDKResource("aws_config_organization_custom_policy_rule", name="Organization Custom Policy Rule") +func resourceOrganizationCustomPolicyRule() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceOrganizationCustomPolicyRuleCreate, ReadWithoutTimeout: resourceOrganizationCustomPolicyRuleRead, @@ -70,18 +71,19 @@ func ResourceOrganizationCustomPolicyRule() *schema.Resource { }, }, "input_parameters": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + DiffSuppressOnRefresh: true, ValidateFunc: validation.All( validation.StringLenBetween(0, 2048), validation.StringIsJSON, ), }, "maximum_execution_frequency": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(configservice.MaximumExecutionFrequency_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.MaximumExecutionFrequency](), }, "name": { Type: schema.TypeString, @@ -129,86 +131,76 @@ func ResourceOrganizationCustomPolicyRule() *schema.Resource { MinItems: 1, MaxItems: 3, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - "ConfigurationItemChangeNotification", - "OversizedConfigurationItemChangeNotification", - }, false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[types.OrganizationConfigRuleTriggerTypeNoSN](), }, }, }, } } -const ( - ResNameOrganizationCustomPolicyRule = "Organization Custom Policy Rule" -) - func resourceOrganizationCustomPolicyRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) name := d.Get("name").(string) - - in := &configservice.PutOrganizationConfigRuleInput{ + input := &configservice.PutOrganizationConfigRuleInput{ OrganizationConfigRuleName: aws.String(name), - OrganizationCustomPolicyRuleMetadata: &configservice.OrganizationCustomPolicyRuleMetadata{ + OrganizationCustomPolicyRuleMetadata: &types.OrganizationCustomPolicyRuleMetadata{ + OrganizationConfigRuleTriggerTypes: flex.ExpandStringyValueSet[types.OrganizationConfigRuleTriggerTypeNoSN](d.Get("trigger_types").(*schema.Set)), PolicyRuntime: aws.String(d.Get("policy_runtime").(string)), PolicyText: aws.String(d.Get("policy_text").(string)), - OrganizationConfigRuleTriggerTypes: flex.ExpandStringSet(d.Get("trigger_types").(*schema.Set)), }, } - if v, ok := d.GetOk("debug_log_delivery_accounts"); ok { - in.OrganizationCustomPolicyRuleMetadata.DebugLogDeliveryAccounts = flex.ExpandStringSet(v.(*schema.Set)) + if v, ok := d.GetOk("debug_log_delivery_accounts"); ok && v.(*schema.Set).Len() > 0 { + input.OrganizationCustomPolicyRuleMetadata.DebugLogDeliveryAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("description"); ok { - in.OrganizationCustomPolicyRuleMetadata.Description = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.Description = aws.String(v.(string)) } if v, ok := d.GetOk("excluded_accounts"); ok && v.(*schema.Set).Len() > 0 { - in.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameters"); ok { - in.OrganizationCustomPolicyRuleMetadata.InputParameters = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.InputParameters = aws.String(v.(string)) } if v, ok := d.GetOk("maximum_execution_frequency"); ok { - in.OrganizationCustomPolicyRuleMetadata.MaximumExecutionFrequency = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v.(string)) } if v, ok := d.GetOk("resource_id_scope"); ok { - in.OrganizationCustomPolicyRuleMetadata.ResourceIdScope = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.ResourceIdScope = aws.String(v.(string)) } if v, ok := d.GetOk("resource_types_scope"); ok && v.(*schema.Set).Len() > 0 { - in.OrganizationCustomPolicyRuleMetadata.ResourceTypesScope = flex.ExpandStringSet(v.(*schema.Set)) + input.OrganizationCustomPolicyRuleMetadata.ResourceTypesScope = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("tag_key_scope"); ok { - in.OrganizationCustomPolicyRuleMetadata.TagKeyScope = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.TagKeyScope = aws.String(v.(string)) } if v, ok := d.GetOk("tag_value_scope"); ok { - in.OrganizationCustomPolicyRuleMetadata.TagValueScope = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.TagValueScope = aws.String(v.(string)) } - out, err := conn.PutOrganizationConfigRuleWithContext(ctx, in) + _, err := tfresource.RetryWhenIsA[*types.OrganizationAccessDeniedException](ctx, organizationsPropagationTimeout, func() (interface{}, error) { + return conn.PutOrganizationConfigRule(ctx, input) + }) if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionCreating, ResNameOrganizationCustomPolicyRule, name, err) - } - - if out == nil || out.OrganizationConfigRuleArn == nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionCreating, ResNameOrganizationCustomPolicyRule, name, errors.New("empty output")) + return sdkdiag.AppendErrorf(diags, "creating ConfigService Organization Custom Policy Rule (%s): %s", name, err) } d.SetId(name) - if err := waitForOrganizationRuleStatusCreateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionWaitingForCreation, ResNameOrganizationCustomPolicyRule, d.Id(), err) + if _, err := waitOrganizationConfigRuleCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Custom Policy Rule (%s) create: %s", d.Id(), err) } return append(diags, resourceOrganizationCustomPolicyRuleRead(ctx, d, meta)...) @@ -216,66 +208,41 @@ func resourceOrganizationCustomPolicyRuleCreate(ctx context.Context, d *schema.R func resourceOrganizationCustomPolicyRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + configRule, err := findOrganizationCustomPolicyRuleByName(ctx, conn, d.Id()) - rule, err := FindOrganizationConfigRule(ctx, conn, d.Id()) if !d.IsNewResource() && tfresource.NotFound(err) { - log.Printf("[WARN] Config %s (%s) not found, removing from state", ResNameOrganizationCustomPolicyRule, d.Id()) + log.Printf("[WARN] ConfigService Organization Custom Policy Rule (%s) not found, removing from state", d.Id()) d.SetId("") return diags } - if rule.OrganizationManagedRuleMetadata != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomPolicyRule, d.Id(), errors.New("expected ResNameOrganizationCustomPolicy, found Organization Managed Rule")) - } - - if rule.OrganizationCustomRuleMetadata != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomPolicyRule, d.Id(), errors.New("expected ResNameOrganizationCustomPolicy, found Organization Custom Rule")) - } - - if rule.OrganizationCustomPolicyRuleMetadata == nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomPolicyRule, d.Id(), errors.New("empty metadata")) - } - - in := &configservice.GetOrganizationCustomRulePolicyInput{ - OrganizationConfigRuleName: aws.String(d.Id()), - } - policy, err := conn.GetOrganizationCustomRulePolicyWithContext(ctx, in) - if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomPolicyRule, d.Id(), err) - } - - d.Set("arn", rule.OrganizationConfigRuleArn) - - if err := d.Set("debug_log_delivery_accounts", aws.StringValueSlice(rule.OrganizationCustomPolicyRuleMetadata.DebugLogDeliveryAccounts)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionSetting, ResNameOrganizationCustomPolicyRule, d.Id(), err) - } - - d.Set("description", rule.OrganizationCustomPolicyRuleMetadata.Description) - - if err := d.Set("excluded_accounts", aws.StringValueSlice(rule.ExcludedAccounts)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionSetting, ResNameOrganizationCustomPolicyRule, d.Id(), err) - } - - d.Set("input_parameters", rule.OrganizationCustomPolicyRuleMetadata.InputParameters) - d.Set("policy_runtime", rule.OrganizationCustomPolicyRuleMetadata.PolicyRuntime) - d.Set("policy_text", policy.PolicyText) - d.Set("maximum_execution_frequency", rule.OrganizationCustomPolicyRuleMetadata.MaximumExecutionFrequency) - d.Set("name", rule.OrganizationConfigRuleName) - d.Set("resource_id_scope", rule.OrganizationCustomPolicyRuleMetadata.ResourceIdScope) - - if err := d.Set("resource_types_scope", aws.StringValueSlice(rule.OrganizationCustomPolicyRuleMetadata.ResourceTypesScope)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionSetting, ResNameOrganizationCustomPolicyRule, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "reading ConfigService Organization Custom Policy Rule (%s): %s", d.Id(), err) } - d.Set("tag_key_scope", rule.OrganizationCustomPolicyRuleMetadata.TagKeyScope) - d.Set("tag_value_scope", rule.OrganizationCustomPolicyRuleMetadata.TagValueScope) + policy, err := findOrganizationCustomRulePolicyByName(ctx, conn, d.Id()) - if err := d.Set("trigger_types", aws.StringValueSlice(rule.OrganizationCustomPolicyRuleMetadata.OrganizationConfigRuleTriggerTypes)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionSetting, ResNameOrganizationCustomPolicyRule, d.Id(), err) - } + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ConfigService Organization Custom Policy Rule (%s) policy: %s", d.Id(), err) + } + + customPolicyRule := configRule.OrganizationCustomPolicyRuleMetadata + d.Set("arn", configRule.OrganizationConfigRuleArn) + d.Set("debug_log_delivery_accounts", customPolicyRule.DebugLogDeliveryAccounts) + d.Set("description", customPolicyRule.Description) + d.Set("excluded_accounts", configRule.ExcludedAccounts) + d.Set("input_parameters", customPolicyRule.InputParameters) + d.Set("policy_runtime", customPolicyRule.PolicyRuntime) + d.Set("policy_text", policy) + d.Set("maximum_execution_frequency", customPolicyRule.MaximumExecutionFrequency) + d.Set("name", configRule.OrganizationConfigRuleName) + d.Set("resource_id_scope", customPolicyRule.ResourceIdScope) + d.Set("resource_types_scope", customPolicyRule.ResourceTypesScope) + d.Set("tag_key_scope", customPolicyRule.TagKeyScope) + d.Set("tag_value_scope", customPolicyRule.TagValueScope) + d.Set("trigger_types", customPolicyRule.OrganizationConfigRuleTriggerTypes) return diags } @@ -283,62 +250,61 @@ func resourceOrganizationCustomPolicyRuleRead(ctx context.Context, d *schema.Res func resourceOrganizationCustomPolicyRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - in := &configservice.PutOrganizationConfigRuleInput{ + input := &configservice.PutOrganizationConfigRuleInput{ OrganizationConfigRuleName: aws.String(d.Id()), - OrganizationCustomPolicyRuleMetadata: &configservice.OrganizationCustomPolicyRuleMetadata{ - PolicyText: aws.String(d.Get("policy_text").(string)), + OrganizationCustomPolicyRuleMetadata: &types.OrganizationCustomPolicyRuleMetadata{ + OrganizationConfigRuleTriggerTypes: flex.ExpandStringyValueSet[types.OrganizationConfigRuleTriggerTypeNoSN](d.Get("trigger_types").(*schema.Set)), PolicyRuntime: aws.String(d.Get("policy_runtime").(string)), - OrganizationConfigRuleTriggerTypes: flex.ExpandStringSet(d.Get("trigger_types").(*schema.Set)), + PolicyText: aws.String(d.Get("policy_text").(string)), }, } if v, ok := d.GetOk("debug_log_delivery_accounts"); ok && v.(*schema.Set).Len() > 0 { - in.OrganizationCustomPolicyRuleMetadata.DebugLogDeliveryAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.OrganizationCustomPolicyRuleMetadata.DebugLogDeliveryAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("description"); ok { - in.OrganizationCustomPolicyRuleMetadata.Description = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.Description = aws.String(v.(string)) } if v, ok := d.GetOk("excluded_accounts"); ok && v.(*schema.Set).Len() > 0 { - in.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameters"); ok { - in.OrganizationCustomPolicyRuleMetadata.InputParameters = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.InputParameters = aws.String(v.(string)) } if v, ok := d.GetOk("maximum_execution_frequency"); ok { - in.OrganizationCustomPolicyRuleMetadata.MaximumExecutionFrequency = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v.(string)) } if v, ok := d.GetOk("resource_id_scope"); ok { - in.OrganizationCustomPolicyRuleMetadata.ResourceIdScope = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.ResourceIdScope = aws.String(v.(string)) } if v, ok := d.GetOk("resource_types_scope"); ok && v.(*schema.Set).Len() > 0 { - in.OrganizationCustomPolicyRuleMetadata.ResourceTypesScope = flex.ExpandStringSet(v.(*schema.Set)) + input.OrganizationCustomPolicyRuleMetadata.ResourceTypesScope = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("tag_key_scope"); ok { - in.OrganizationCustomPolicyRuleMetadata.TagKeyScope = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.TagKeyScope = aws.String(v.(string)) } if v, ok := d.GetOk("tag_value_scope"); ok { - in.OrganizationCustomPolicyRuleMetadata.TagValueScope = aws.String(v.(string)) + input.OrganizationCustomPolicyRuleMetadata.TagValueScope = aws.String(v.(string)) } - log.Printf("[DEBUG] Updating ConfigService %s (%s): %#v", ResNameOrganizationCustomPolicyRule, d.Id(), in) - _, err := conn.PutOrganizationConfigRuleWithContext(ctx, in) + _, err := conn.PutOrganizationConfigRule(ctx, input) + if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionUpdating, ResNameOrganizationCustomPolicyRule, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating ConfigService Organization Custom Policy Rule (%s): %s", d.Id(), err) } - err = waitForOrganizationRuleStatusUpdateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)) - if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionWaitingForUpdate, ResNameOrganizationCustomPolicyRule, d.Id(), err) + if _, err := waitOrganizationConfigRuleUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Custom Policy Rule (%s) update: %s", d.Id(), err) } return append(diags, resourceOrganizationCustomPolicyRuleRead(ctx, d, meta)...) @@ -346,28 +312,68 @@ func resourceOrganizationCustomPolicyRuleUpdate(ctx context.Context, d *schema.R func resourceOrganizationCustomPolicyRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + const ( + timeout = 2 * time.Minute + ) + log.Printf("[DEBUG] Deleting ConfigService Organization Custom Policy Rule: %s", d.Id()) + _, err := tfresource.RetryWhenIsA[*types.ResourceInUseException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteOrganizationConfigRule(ctx, &configservice.DeleteOrganizationConfigRuleInput{ + OrganizationConfigRuleName: aws.String(d.Id()), + }) + }) + + if errs.IsA[*types.NoSuchOrganizationConfigRuleException](err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { + return diags + } - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Organization Custom Policy Rule (%s): %s", d.Id(), err) + } - log.Printf("[INFO] Deleting ConfigService %s %s", ResNameOrganizationCustomPolicyRule, d.Id()) + if _, err := waitOrganizationConfigRuleDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Custom Policy Rule (%s) delete: %s", d.Id(), err) + } - in := &configservice.DeleteOrganizationConfigRuleInput{ - OrganizationConfigRuleName: aws.String(d.Id()), + return diags +} + +func findOrganizationCustomPolicyRuleByName(ctx context.Context, conn *configservice.Client, name string) (*types.OrganizationConfigRule, error) { + output, err := findOrganizationConfigRuleByName(ctx, conn, name) + + if err != nil { + return nil, err } - _, err := conn.DeleteOrganizationConfigRuleWithContext(ctx, in) + if output.OrganizationCustomPolicyRuleMetadata == nil { + return nil, tfresource.NewEmptyResultError(nil) + } - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConfigRuleException) { - return diags + return output, nil +} + +func findOrganizationCustomRulePolicyByName(ctx context.Context, conn *configservice.Client, name string) (*string, error) { + input := &configservice.GetOrganizationCustomRulePolicyInput{ + OrganizationConfigRuleName: aws.String(name), + } + + output, err := conn.GetOrganizationCustomRulePolicy(ctx, input) + + if errs.IsA[*types.NoSuchOrganizationConfigRuleException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } } if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionDeleting, ResNameOrganizationCustomPolicyRule, d.Id(), err) + return nil, err } - if err := waitForOrganizationRuleStatusDeleteSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionWaitingForDeletion, ResNameOrganizationCustomPolicyRule, d.Id(), err) + if output == nil { + return nil, tfresource.NewEmptyResultError(input) } - return diags + return output.PolicyText, nil } diff --git a/internal/service/configservice/organization_custom_policy_rule_test.go b/internal/service/configservice/organization_custom_policy_rule_test.go index 394c8719804..49685fc4da9 100644 --- a/internal/service/configservice/organization_custom_policy_rule_test.go +++ b/internal/service/configservice/organization_custom_policy_rule_test.go @@ -5,38 +5,32 @@ package configservice_test import ( "context" - "errors" "fmt" "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" - tfconfigservice "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) -func TestAccConfigServiceOrganizationCustomPolicyRule_basic(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } +func testAccOrganizationCustomPolicyRule_basic(t *testing.T) { ctx := acctest.Context(t) - - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_policy_rule.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, configservice.EndpointsID) + acctest.PreCheckPartitionHasService(t, names.ConfigServiceEndpointID) acctest.PreCheckOrganizationsAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), @@ -70,20 +64,16 @@ func TestAccConfigServiceOrganizationCustomPolicyRule_basic(t *testing.T) { }) } -func TestAccConfigServiceOrganizationCustomPolicyRule_disappears(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } +func testAccOrganizationCustomPolicyRule_disappears(t *testing.T) { ctx := acctest.Context(t) - - var organizationcustompolicy configservice.OrganizationConfigRule + var organizationcustompolicy types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_policy_rule.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, configservice.EndpointsID) + acctest.PreCheckPartitionHasService(t, names.ConfigServiceEndpointID) acctest.PreCheckOrganizationsAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), @@ -94,7 +84,7 @@ func TestAccConfigServiceOrganizationCustomPolicyRule_disappears(t *testing.T) { Config: testAccOrganizationCustomPolicyRuleConfig_basic(rName), Check: resource.ComposeTestCheckFunc( testAccCheckOrganizationCustomPolicyRuleExists(ctx, resourceName, &organizationcustompolicy), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfconfigservice.ResourceOrganizationCustomPolicyRule(), resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfconfig.ResourceOrganizationCustomPolicyRule(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -102,20 +92,16 @@ func TestAccConfigServiceOrganizationCustomPolicyRule_disappears(t *testing.T) { }) } -func TestAccConfigServiceOrganizationCustomPolicyRule_PolicyText(t *testing.T) { - if testing.Short() { - t.Skip("skipping long-running test in short mode") - } +func testAccOrganizationCustomPolicyRule_PolicyText(t *testing.T) { ctx := acctest.Context(t) - - var organizationcustompolicy configservice.OrganizationConfigRule + var organizationcustompolicy types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_policy_rule.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) - acctest.PreCheckPartitionHasService(t, configservice.EndpointsID) + acctest.PreCheckPartitionHasService(t, names.ConfigServiceEndpointID) acctest.PreCheckOrganizationsAccount(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), @@ -146,50 +132,46 @@ func TestAccConfigServiceOrganizationCustomPolicyRule_PolicyText(t *testing.T) { func testAccCheckOrganizationCustomPolicyRuleDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_organization_custom_policy_rule" { continue } - _, err := tfconfigservice.FindOrganizationConfigRule(ctx, conn, rs.Primary.ID) + _, err := tfconfig.FindOrganizationCustomPolicyRuleByName(ctx, conn, rs.Primary.ID) - if tfresource.NotFound(err) { + if tfresource.NotFound(err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { continue } - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeOrganizationAccessDeniedException) { - continue + if err != nil { + return err } - return create.Error(names.ConfigService, create.ErrActionCheckingDestroyed, tfconfigservice.ResNameOrganizationCustomPolicyRule, rs.Primary.ID, errors.New("not destroyed")) + return fmt.Errorf("ConfigService Organization Custom Policy Rule %s still exists", rs.Primary.ID) } return nil } } -func testAccCheckOrganizationCustomPolicyRuleExists(ctx context.Context, name string, ocr *configservice.OrganizationConfigRule) resource.TestCheckFunc { +func testAccCheckOrganizationCustomPolicyRuleExists(ctx context.Context, n string, v *types.OrganizationConfigRule) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[name] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameOrganizationCustomPolicyRule, name, errors.New("not found")) - } - - if rs.Primary.ID == "" { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameOrganizationCustomPolicyRule, name, errors.New("not set")) + return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - resp, err := tfconfigservice.FindOrganizationConfigRule(ctx, conn, rs.Primary.ID) + output, err := tfconfig.FindOrganizationCustomPolicyRuleByName(ctx, conn, rs.Primary.ID) if err != nil { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameOrganizationCustomPolicyRule, rs.Primary.ID, err) + return err } - *ocr = *resp + *v = *output return nil } diff --git a/internal/service/configservice/organization_custom_rule.go b/internal/service/configservice/organization_custom_rule.go index 1ebbb9f0751..76d2fe1801f 100644 --- a/internal/service/configservice/organization_custom_rule.go +++ b/internal/service/configservice/organization_custom_rule.go @@ -5,30 +5,31 @@ package configservice import ( "context" - "errors" "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "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/create" + "github.com/hashicorp/terraform-provider-aws/internal/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "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" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_config_organization_custom_rule") -func ResourceOrganizationCustomRule() *schema.Resource { +// @SDKResource("aws_config_organization_custom_rule", name="Organization Custom Rule") +func resourceOrganizationCustomRule() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceOrganizationCustomRuleCreate, - DeleteWithoutTimeout: resourceOrganizationCustomRuleDelete, ReadWithoutTimeout: resourceOrganizationCustomRuleRead, UpdateWithoutTimeout: resourceOrganizationCustomRuleUpdate, + DeleteWithoutTimeout: resourceOrganizationCustomRuleDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -60,9 +61,10 @@ func ResourceOrganizationCustomRule() *schema.Resource { }, }, "input_parameters": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + DiffSuppressOnRefresh: true, ValidateFunc: validation.All( validation.StringLenBetween(0, 2048), validation.StringIsJSON, @@ -71,12 +73,12 @@ func ResourceOrganizationCustomRule() *schema.Resource { "lambda_function_arn": { Type: schema.TypeString, Required: true, - ValidateFunc: validation.StringLenBetween(1, 256), + ValidateFunc: verify.ValidARN, }, "maximum_execution_frequency": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(configservice.MaximumExecutionFrequency_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.MaximumExecutionFrequency](), }, "name": { Type: schema.TypeString, @@ -114,12 +116,8 @@ func ResourceOrganizationCustomRule() *schema.Resource { MinItems: 1, MaxItems: 3, Elem: &schema.Schema{ - Type: schema.TypeString, - ValidateFunc: validation.StringInSlice([]string{ - "ConfigurationItemChangeNotification", - "OversizedConfigurationItemChangeNotification", - "ScheduledNotification", - }, false), + Type: schema.TypeString, + ValidateDiagFunc: enum.Validate[types.OrganizationConfigRuleTriggerType](), }, }, }, @@ -128,14 +126,14 @@ func ResourceOrganizationCustomRule() *schema.Resource { func resourceOrganizationCustomRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) - name := d.Get("name").(string) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + name := d.Get("name").(string) input := &configservice.PutOrganizationConfigRuleInput{ OrganizationConfigRuleName: aws.String(name), - OrganizationCustomRuleMetadata: &configservice.OrganizationCustomRuleMetadata{ + OrganizationCustomRuleMetadata: &types.OrganizationCustomRuleMetadata{ LambdaFunctionArn: aws.String(d.Get("lambda_function_arn").(string)), - OrganizationConfigRuleTriggerTypes: flex.ExpandStringSet(d.Get("trigger_types").(*schema.Set)), + OrganizationConfigRuleTriggerTypes: flex.ExpandStringyValueSet[types.OrganizationConfigRuleTriggerType](d.Get("trigger_types").(*schema.Set)), }, } @@ -144,7 +142,7 @@ func resourceOrganizationCustomRuleCreate(ctx context.Context, d *schema.Resourc } if v, ok := d.GetOk("excluded_accounts"); ok && v.(*schema.Set).Len() > 0 { - input.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameters"); ok { @@ -152,7 +150,7 @@ func resourceOrganizationCustomRuleCreate(ctx context.Context, d *schema.Resourc } if v, ok := d.GetOk("maximum_execution_frequency"); ok { - input.OrganizationCustomRuleMetadata.MaximumExecutionFrequency = aws.String(v.(string)) + input.OrganizationCustomRuleMetadata.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v.(string)) } if v, ok := d.GetOk("resource_id_scope"); ok { @@ -160,7 +158,7 @@ func resourceOrganizationCustomRuleCreate(ctx context.Context, d *schema.Resourc } if v, ok := d.GetOk("resource_types_scope"); ok && v.(*schema.Set).Len() > 0 { - input.OrganizationCustomRuleMetadata.ResourceTypesScope = flex.ExpandStringSet(v.(*schema.Set)) + input.OrganizationCustomRuleMetadata.ResourceTypesScope = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("tag_key_scope"); ok { @@ -171,16 +169,18 @@ func resourceOrganizationCustomRuleCreate(ctx context.Context, d *schema.Resourc input.OrganizationCustomRuleMetadata.TagValueScope = aws.String(v.(string)) } - _, err := conn.PutOrganizationConfigRuleWithContext(ctx, input) + _, err := tfresource.RetryWhenIsA[*types.OrganizationAccessDeniedException](ctx, organizationsPropagationTimeout, func() (interface{}, error) { + return conn.PutOrganizationConfigRule(ctx, input) + }) if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionCreating, ResNameOrganizationCustomRule, name, err) + return sdkdiag.AppendErrorf(diags, "creating ConfigService Organization Custom Rule (%s): %s", name, err) } d.SetId(name) - if err := waitForOrganizationRuleStatusCreateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionWaitingForCreation, ResNameOrganizationCustomRule, d.Id(), err) + if _, err := waitOrganizationConfigRuleCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Custom Rule (%s) create: %s", d.Id(), err) } return append(diags, resourceOrganizationCustomRuleRead(ctx, d, meta)...) @@ -188,78 +188,46 @@ func resourceOrganizationCustomRuleCreate(ctx context.Context, d *schema.Resourc func resourceOrganizationCustomRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - rule, err := DescribeOrganizationConfigRule(ctx, conn, d.Id()) + configRule, err := findOrganizationCustomRuleByName(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConfigRuleException) { - log.Printf("[WARN] Config Organization Custom Rule (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Organization Custom Rule (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomRule, d.Id(), err) - } - - if !d.IsNewResource() && rule == nil { - log.Printf("[WARN] Config Organization Custom Rule (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } - - if d.IsNewResource() && rule == nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomRule, d.Id(), errors.New("empty rule after creation")) - } - - if rule.OrganizationCustomPolicyRuleMetadata != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomRule, d.Id(), errors.New("expected Organization Custom Rule, found Organization Custom Policy Rule")) - } - - if rule.OrganizationManagedRuleMetadata != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomRule, d.Id(), errors.New("expected Organization Custom Rule, found Organization Managed Rule")) - } - - if rule.OrganizationCustomRuleMetadata == nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationCustomRule, d.Id(), errors.New("empty metadata")) - } - - d.Set("arn", rule.OrganizationConfigRuleArn) - d.Set("description", rule.OrganizationCustomRuleMetadata.Description) - - if err := d.Set("excluded_accounts", aws.StringValueSlice(rule.ExcludedAccounts)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionSetting, ResNameOrganizationCustomRule, d.Id(), err) - } - - d.Set("input_parameters", rule.OrganizationCustomRuleMetadata.InputParameters) - d.Set("lambda_function_arn", rule.OrganizationCustomRuleMetadata.LambdaFunctionArn) - d.Set("maximum_execution_frequency", rule.OrganizationCustomRuleMetadata.MaximumExecutionFrequency) - d.Set("name", rule.OrganizationConfigRuleName) - d.Set("resource_id_scope", rule.OrganizationCustomRuleMetadata.ResourceIdScope) - - if err := d.Set("resource_types_scope", aws.StringValueSlice(rule.OrganizationCustomRuleMetadata.ResourceTypesScope)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionSetting, ResNameOrganizationCustomRule, d.Id(), err) - } - - d.Set("tag_key_scope", rule.OrganizationCustomRuleMetadata.TagKeyScope) - d.Set("tag_value_scope", rule.OrganizationCustomRuleMetadata.TagValueScope) - - if err := d.Set("trigger_types", aws.StringValueSlice(rule.OrganizationCustomRuleMetadata.OrganizationConfigRuleTriggerTypes)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionSetting, ResNameOrganizationCustomRule, d.Id(), err) - } + return sdkdiag.AppendErrorf(diags, "reading ConfigService Organization Custom Rule (%s): %s", d.Id(), err) + } + + customRule := configRule.OrganizationCustomRuleMetadata + d.Set("arn", configRule.OrganizationConfigRuleArn) + d.Set("description", customRule.Description) + d.Set("excluded_accounts", configRule.ExcludedAccounts) + d.Set("input_parameters", customRule.InputParameters) + d.Set("lambda_function_arn", customRule.LambdaFunctionArn) + d.Set("maximum_execution_frequency", customRule.MaximumExecutionFrequency) + d.Set("name", configRule.OrganizationConfigRuleName) + d.Set("resource_id_scope", customRule.ResourceIdScope) + d.Set("resource_types_scope", customRule.ResourceTypesScope) + d.Set("tag_key_scope", customRule.TagKeyScope) + d.Set("tag_value_scope", customRule.TagValueScope) + d.Set("trigger_types", customRule.OrganizationConfigRuleTriggerTypes) return diags } func resourceOrganizationCustomRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) input := &configservice.PutOrganizationConfigRuleInput{ OrganizationConfigRuleName: aws.String(d.Id()), - OrganizationCustomRuleMetadata: &configservice.OrganizationCustomRuleMetadata{ + OrganizationCustomRuleMetadata: &types.OrganizationCustomRuleMetadata{ LambdaFunctionArn: aws.String(d.Get("lambda_function_arn").(string)), - OrganizationConfigRuleTriggerTypes: flex.ExpandStringSet(d.Get("trigger_types").(*schema.Set)), + OrganizationConfigRuleTriggerTypes: flex.ExpandStringyValueSet[types.OrganizationConfigRuleTriggerType](d.Get("trigger_types").(*schema.Set)), }, } @@ -268,7 +236,7 @@ func resourceOrganizationCustomRuleUpdate(ctx context.Context, d *schema.Resourc } if v, ok := d.GetOk("excluded_accounts"); ok && v.(*schema.Set).Len() > 0 { - input.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameters"); ok { @@ -276,7 +244,7 @@ func resourceOrganizationCustomRuleUpdate(ctx context.Context, d *schema.Resourc } if v, ok := d.GetOk("maximum_execution_frequency"); ok { - input.OrganizationCustomRuleMetadata.MaximumExecutionFrequency = aws.String(v.(string)) + input.OrganizationCustomRuleMetadata.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v.(string)) } if v, ok := d.GetOk("resource_id_scope"); ok { @@ -284,7 +252,7 @@ func resourceOrganizationCustomRuleUpdate(ctx context.Context, d *schema.Resourc } if v, ok := d.GetOk("resource_types_scope"); ok && v.(*schema.Set).Len() > 0 { - input.OrganizationCustomRuleMetadata.ResourceTypesScope = flex.ExpandStringSet(v.(*schema.Set)) + input.OrganizationCustomRuleMetadata.ResourceTypesScope = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("tag_key_scope"); ok { @@ -295,14 +263,14 @@ func resourceOrganizationCustomRuleUpdate(ctx context.Context, d *schema.Resourc input.OrganizationCustomRuleMetadata.TagValueScope = aws.String(v.(string)) } - _, err := conn.PutOrganizationConfigRuleWithContext(ctx, input) + _, err := conn.PutOrganizationConfigRule(ctx, input) if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionUpdating, ResNameOrganizationCustomRule, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating ConfigService Organization Custom Rule (%s): %s", d.Id(), err) } - if err := waitForOrganizationRuleStatusUpdateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionWaitingForUpdate, ResNameOrganizationCustomRule, d.Id(), err) + if _, err := waitOrganizationConfigRuleUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Custom Rule (%s) update: %s", d.Id(), err) } return append(diags, resourceOrganizationCustomRuleRead(ctx, d, meta)...) @@ -310,21 +278,43 @@ func resourceOrganizationCustomRuleUpdate(ctx context.Context, d *schema.Resourc func resourceOrganizationCustomRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + const ( + timeout = 2 * time.Minute + ) + log.Printf("[DEBUG] Deleting ConfigService Organization Custom Rule: %s", d.Id()) + _, err := tfresource.RetryWhenIsA[*types.ResourceInUseException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteOrganizationConfigRule(ctx, &configservice.DeleteOrganizationConfigRuleInput{ + OrganizationConfigRuleName: aws.String(d.Id()), + }) + }) + + if errs.IsA[*types.NoSuchOrganizationConfigRuleException](err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { + return diags + } - input := &configservice.DeleteOrganizationConfigRuleInput{ - OrganizationConfigRuleName: aws.String(d.Id()), + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Organization Custom Rule (%s): %s", d.Id(), err) + } + + if _, err := waitOrganizationConfigRuleDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Custom Rule (%s) delete: %s", d.Id(), err) } - _, err := conn.DeleteOrganizationConfigRuleWithContext(ctx, input) + return diags +} + +func findOrganizationCustomRuleByName(ctx context.Context, conn *configservice.Client, name string) (*types.OrganizationConfigRule, error) { + output, err := findOrganizationConfigRuleByName(ctx, conn, name) if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionDeleting, ResNameOrganizationCustomRule, d.Id(), err) + return nil, err } - if err := waitForOrganizationRuleStatusDeleteSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionWaitingForDeletion, ResNameOrganizationCustomRule, d.Id(), err) + if output.OrganizationCustomRuleMetadata == nil { + return nil, tfresource.NewEmptyResultError(nil) } - return diags + return output, nil } diff --git a/internal/service/configservice/organization_custom_rule_test.go b/internal/service/configservice/organization_custom_rule_test.go index 47cc2f41c47..3e35db4c5e6 100644 --- a/internal/service/configservice/organization_custom_rule_test.go +++ b/internal/service/configservice/organization_custom_rule_test.go @@ -5,26 +5,25 @@ package configservice_test import ( "context" - "errors" "fmt" "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" + "github.com/hashicorp/terraform-provider-aws/internal/errs" tfconfigservice "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccOrganizationCustomRule_basic(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) lambdaFunctionResourceName := "aws_lambda_function.test" resourceName := "aws_config_organization_custom_rule.test" @@ -64,7 +63,7 @@ func testAccOrganizationCustomRule_basic(t *testing.T) { func testAccOrganizationCustomRule_disappears(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -106,7 +105,7 @@ func testAccOrganizationCustomRule_errorHandling(t *testing.T) { func testAccOrganizationCustomRule_Description(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -141,7 +140,7 @@ func testAccOrganizationCustomRule_Description(t *testing.T) { func testAccOrganizationCustomRule_ExcludedAccounts(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -176,7 +175,7 @@ func testAccOrganizationCustomRule_ExcludedAccounts(t *testing.T) { func testAccOrganizationCustomRule_InputParameters(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -214,7 +213,7 @@ func testAccOrganizationCustomRule_InputParameters(t *testing.T) { func testAccOrganizationCustomRule_lambdaFunctionARN(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) lambdaFunctionResourceName1 := "aws_lambda_function.test" lambdaFunctionResourceName2 := "aws_lambda_function.test2" @@ -251,7 +250,7 @@ func testAccOrganizationCustomRule_lambdaFunctionARN(t *testing.T) { func testAccOrganizationCustomRule_MaximumExecutionFrequency(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -286,7 +285,7 @@ func testAccOrganizationCustomRule_MaximumExecutionFrequency(t *testing.T) { func testAccOrganizationCustomRule_ResourceIdScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -321,7 +320,7 @@ func testAccOrganizationCustomRule_ResourceIdScope(t *testing.T) { func testAccOrganizationCustomRule_ResourceTypesScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -356,7 +355,7 @@ func testAccOrganizationCustomRule_ResourceTypesScope(t *testing.T) { func testAccOrganizationCustomRule_TagKeyScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -391,7 +390,7 @@ func testAccOrganizationCustomRule_TagKeyScope(t *testing.T) { func testAccOrganizationCustomRule_TagValueScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -426,7 +425,7 @@ func testAccOrganizationCustomRule_TagValueScope(t *testing.T) { func testAccOrganizationCustomRule_TriggerTypes(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_custom_rule.test" @@ -459,26 +458,22 @@ func testAccOrganizationCustomRule_TriggerTypes(t *testing.T) { }) } -func testAccCheckOrganizationCustomRuleExists(ctx context.Context, resourceName string, ocr *configservice.OrganizationConfigRule) resource.TestCheckFunc { +func testAccCheckOrganizationCustomRuleExists(ctx context.Context, n string, v *types.OrganizationConfigRule) resource.TestCheckFunc { return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[resourceName] + rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameOrganizationCustomRule, resourceName, errors.New("not found")) + return fmt.Errorf("Not found: %s", n) } - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - rule, err := tfconfigservice.DescribeOrganizationConfigRule(ctx, conn, rs.Primary.ID) + output, err := tfconfigservice.FindOrganizationCustomRuleByName(ctx, conn, rs.Primary.ID) if err != nil { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameOrganizationCustomRule, resourceName, err) + return err } - if rule == nil { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameOrganizationCustomRule, resourceName, errors.New("empty response")) - } - - *ocr = *rule + *v = *output return nil } @@ -486,26 +481,24 @@ func testAccCheckOrganizationCustomRuleExists(ctx context.Context, resourceName func testAccCheckOrganizationCustomRuleDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_organization_custom_rule" { continue } - rule, err := tfconfigservice.DescribeOrganizationConfigRule(ctx, conn, rs.Primary.ID) + _, err := tfconfigservice.FindOrganizationCustomRuleByName(ctx, conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConfigRuleException) { + if tfresource.NotFound(err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { continue } if err != nil { - return create.Error(names.ConfigService, create.ErrActionCheckingDestroyed, tfconfigservice.ResNameOrganizationCustomRule, rs.Primary.ID, err) + return err } - if rule != nil { - return create.Error(names.ConfigService, create.ErrActionCheckingDestroyed, tfconfigservice.ResNameOrganizationCustomRule, rs.Primary.ID, errors.New("still exists")) - } + return fmt.Errorf("ConfigService Organization Custom Rule %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/configservice/organization_managed_rule.go b/internal/service/configservice/organization_managed_rule.go index f4544e54550..cf8ec9bc271 100644 --- a/internal/service/configservice/organization_managed_rule.go +++ b/internal/service/configservice/organization_managed_rule.go @@ -6,30 +6,33 @@ package configservice import ( "context" "errors" + "fmt" "log" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" "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" - "github.com/hashicorp/terraform-provider-aws/names" ) -// @SDKResource("aws_config_organization_managed_rule") -func ResourceOrganizationManagedRule() *schema.Resource { +// @SDKResource("aws_config_organization_managed_rule", name="Organization Managed Rule") +func resourceOrganizationManagedRule() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceOrganizationManagedRuleCreate, - DeleteWithoutTimeout: resourceOrganizationManagedRuleDelete, ReadWithoutTimeout: resourceOrganizationManagedRuleRead, UpdateWithoutTimeout: resourceOrganizationManagedRuleUpdate, + DeleteWithoutTimeout: resourceOrganizationManagedRuleDelete, Importer: &schema.ResourceImporter{ StateContext: schema.ImportStatePassthroughContext, @@ -37,8 +40,8 @@ func ResourceOrganizationManagedRule() *schema.Resource { Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(5 * time.Minute), - Delete: schema.DefaultTimeout(5 * time.Minute), Update: schema.DefaultTimeout(5 * time.Minute), + Delete: schema.DefaultTimeout(5 * time.Minute), }, Schema: map[string]*schema.Schema{ @@ -61,18 +64,19 @@ func ResourceOrganizationManagedRule() *schema.Resource { }, }, "input_parameters": { - Type: schema.TypeString, - Optional: true, - DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + Type: schema.TypeString, + Optional: true, + DiffSuppressFunc: verify.SuppressEquivalentJSONDiffs, + DiffSuppressOnRefresh: true, ValidateFunc: validation.All( validation.StringLenBetween(0, 2048), validation.StringIsJSON, ), }, "maximum_execution_frequency": { - Type: schema.TypeString, - Optional: true, - ValidateFunc: validation.StringInSlice(configservice.MaximumExecutionFrequency_Values(), false), + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: enum.Validate[types.MaximumExecutionFrequency](), }, "name": { Type: schema.TypeString, @@ -115,12 +119,12 @@ func ResourceOrganizationManagedRule() *schema.Resource { func resourceOrganizationManagedRuleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) - name := d.Get("name").(string) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + name := d.Get("name").(string) input := &configservice.PutOrganizationConfigRuleInput{ OrganizationConfigRuleName: aws.String(name), - OrganizationManagedRuleMetadata: &configservice.OrganizationManagedRuleMetadata{ + OrganizationManagedRuleMetadata: &types.OrganizationManagedRuleMetadata{ RuleIdentifier: aws.String(d.Get("rule_identifier").(string)), }, } @@ -130,7 +134,7 @@ func resourceOrganizationManagedRuleCreate(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("excluded_accounts"); ok && v.(*schema.Set).Len() > 0 { - input.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameters"); ok { @@ -138,7 +142,7 @@ func resourceOrganizationManagedRuleCreate(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("maximum_execution_frequency"); ok { - input.OrganizationManagedRuleMetadata.MaximumExecutionFrequency = aws.String(v.(string)) + input.OrganizationManagedRuleMetadata.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v.(string)) } if v, ok := d.GetOk("resource_id_scope"); ok { @@ -146,7 +150,7 @@ func resourceOrganizationManagedRuleCreate(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("resource_types_scope"); ok && v.(*schema.Set).Len() > 0 { - input.OrganizationManagedRuleMetadata.ResourceTypesScope = flex.ExpandStringSet(v.(*schema.Set)) + input.OrganizationManagedRuleMetadata.ResourceTypesScope = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("tag_key_scope"); ok { @@ -157,16 +161,18 @@ func resourceOrganizationManagedRuleCreate(ctx context.Context, d *schema.Resour input.OrganizationManagedRuleMetadata.TagValueScope = aws.String(v.(string)) } - _, err := conn.PutOrganizationConfigRuleWithContext(ctx, input) + _, err := tfresource.RetryWhenIsA[*types.OrganizationAccessDeniedException](ctx, organizationsPropagationTimeout, func() (interface{}, error) { + return conn.PutOrganizationConfigRule(ctx, input) + }) if err != nil { - return sdkdiag.AppendErrorf(diags, "creating Config Organization Managed Rule (%s): %s", name, err) + return sdkdiag.AppendErrorf(diags, "creating ConfigService Organization Managed Rule (%s): %s", name, err) } d.SetId(name) - if err := waitForOrganizationRuleStatusCreateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Config Organization Managed Rule (%s) creation: %s", d.Id(), err) + if _, err := waitOrganizationConfigRuleCreated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutCreate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Managed Rule (%s) create: %s", d.Id(), err) } return append(diags, resourceOrganizationManagedRuleRead(ctx, d, meta)...) @@ -174,72 +180,43 @@ func resourceOrganizationManagedRuleCreate(ctx context.Context, d *schema.Resour func resourceOrganizationManagedRuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - rule, err := DescribeOrganizationConfigRule(ctx, conn, d.Id()) + configRule, err := findOrganizationManagedRuleByName(ctx, conn, d.Id()) - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConfigRuleException) { - log.Printf("[WARN] Config Organization Managed Rule (%s) not found, removing from state", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Organization Managed Rule (%s) not found, removing from state", d.Id()) d.SetId("") return diags } if err != nil { - return sdkdiag.AppendErrorf(diags, "describing Config Organization Managed Rule (%s): %s", d.Id(), err) - } - - if !d.IsNewResource() && rule == nil { - log.Printf("[WARN] Config Organization Managed Rule (%s) not found, removing from state", d.Id()) - d.SetId("") - return diags - } - - if d.IsNewResource() && rule == nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameOrganizationManagedRule, d.Id(), errors.New("empty rule after creation")) - } - - if rule.OrganizationCustomPolicyRuleMetadata != nil { - return sdkdiag.AppendErrorf(diags, "expected Organization Custom Rule, found Organization Custom Policy Rule: %s", d.Id()) - } - - if rule.OrganizationCustomRuleMetadata != nil { - return sdkdiag.AppendErrorf(diags, "expected Config Organization Managed Rule, found Config Organization Custom Rule: %s", d.Id()) - } - - if rule.OrganizationManagedRuleMetadata == nil { - return sdkdiag.AppendErrorf(diags, "describing Config Organization Managed Rule (%s): empty metadata", d.Id()) - } - - d.Set("arn", rule.OrganizationConfigRuleArn) - d.Set("description", rule.OrganizationManagedRuleMetadata.Description) - - if err := d.Set("excluded_accounts", aws.StringValueSlice(rule.ExcludedAccounts)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting excluded_accounts: %s", err) - } - - d.Set("input_parameters", rule.OrganizationManagedRuleMetadata.InputParameters) - d.Set("maximum_execution_frequency", rule.OrganizationManagedRuleMetadata.MaximumExecutionFrequency) - d.Set("name", rule.OrganizationConfigRuleName) - d.Set("resource_id_scope", rule.OrganizationManagedRuleMetadata.ResourceIdScope) - - if err := d.Set("resource_types_scope", aws.StringValueSlice(rule.OrganizationManagedRuleMetadata.ResourceTypesScope)); err != nil { - return sdkdiag.AppendErrorf(diags, "setting resource_types_scope: %s", err) - } - - d.Set("rule_identifier", rule.OrganizationManagedRuleMetadata.RuleIdentifier) - d.Set("tag_key_scope", rule.OrganizationManagedRuleMetadata.TagKeyScope) - d.Set("tag_value_scope", rule.OrganizationManagedRuleMetadata.TagValueScope) + return sdkdiag.AppendErrorf(diags, "reading ConfigService Organization Managed Rule (%s): %s", d.Id(), err) + } + + managedRule := configRule.OrganizationManagedRuleMetadata + d.Set("arn", configRule.OrganizationConfigRuleArn) + d.Set("description", managedRule.Description) + d.Set("excluded_accounts", configRule.ExcludedAccounts) + d.Set("input_parameters", managedRule.InputParameters) + d.Set("maximum_execution_frequency", managedRule.MaximumExecutionFrequency) + d.Set("name", configRule.OrganizationConfigRuleName) + d.Set("resource_id_scope", managedRule.ResourceIdScope) + d.Set("resource_types_scope", managedRule.ResourceTypesScope) + d.Set("rule_identifier", managedRule.RuleIdentifier) + d.Set("tag_key_scope", managedRule.TagKeyScope) + d.Set("tag_value_scope", managedRule.TagValueScope) return diags } func resourceOrganizationManagedRuleUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) input := &configservice.PutOrganizationConfigRuleInput{ OrganizationConfigRuleName: aws.String(d.Id()), - OrganizationManagedRuleMetadata: &configservice.OrganizationManagedRuleMetadata{ + OrganizationManagedRuleMetadata: &types.OrganizationManagedRuleMetadata{ RuleIdentifier: aws.String(d.Get("rule_identifier").(string)), }, } @@ -249,7 +226,7 @@ func resourceOrganizationManagedRuleUpdate(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("excluded_accounts"); ok && v.(*schema.Set).Len() > 0 { - input.ExcludedAccounts = flex.ExpandStringSet(v.(*schema.Set)) + input.ExcludedAccounts = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("input_parameters"); ok { @@ -257,7 +234,7 @@ func resourceOrganizationManagedRuleUpdate(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("maximum_execution_frequency"); ok { - input.OrganizationManagedRuleMetadata.MaximumExecutionFrequency = aws.String(v.(string)) + input.OrganizationManagedRuleMetadata.MaximumExecutionFrequency = types.MaximumExecutionFrequency(v.(string)) } if v, ok := d.GetOk("resource_id_scope"); ok { @@ -265,7 +242,7 @@ func resourceOrganizationManagedRuleUpdate(ctx context.Context, d *schema.Resour } if v, ok := d.GetOk("resource_types_scope"); ok && v.(*schema.Set).Len() > 0 { - input.OrganizationManagedRuleMetadata.ResourceTypesScope = flex.ExpandStringSet(v.(*schema.Set)) + input.OrganizationManagedRuleMetadata.ResourceTypesScope = flex.ExpandStringValueSet(v.(*schema.Set)) } if v, ok := d.GetOk("tag_key_scope"); ok { @@ -276,14 +253,14 @@ func resourceOrganizationManagedRuleUpdate(ctx context.Context, d *schema.Resour input.OrganizationManagedRuleMetadata.TagValueScope = aws.String(v.(string)) } - _, err := conn.PutOrganizationConfigRuleWithContext(ctx, input) + _, err := conn.PutOrganizationConfigRule(ctx, input) if err != nil { - return sdkdiag.AppendErrorf(diags, "updating Config Organization Managed Rule (%s): %s", d.Id(), err) + return sdkdiag.AppendErrorf(diags, "updating ConfigService Organization Managed Rule (%s): %s", d.Id(), err) } - if err := waitForOrganizationRuleStatusUpdateSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Config Organization Managed Rule (%s) update: %s", d.Id(), err) + if _, err := waitOrganizationConfigRuleUpdated(ctx, conn, d.Id(), d.Timeout(schema.TimeoutUpdate)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Managed Rule (%s) update: %s", d.Id(), err) } return append(diags, resourceOrganizationManagedRuleRead(ctx, d, meta)...) @@ -291,21 +268,290 @@ func resourceOrganizationManagedRuleUpdate(ctx context.Context, d *schema.Resour func resourceOrganizationManagedRuleDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) + + const ( + timeout = 2 * time.Minute + ) + log.Printf("[DEBUG] Deleting ConfigService Organization Managed Rule: %s", d.Id()) + _, err := tfresource.RetryWhenIsA[*types.ResourceInUseException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteOrganizationConfigRule(ctx, &configservice.DeleteOrganizationConfigRuleInput{ + OrganizationConfigRuleName: aws.String(d.Id()), + }) + }) + + if errs.IsA[*types.NoSuchOrganizationConfigRuleException](err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { + return diags + } - input := &configservice.DeleteOrganizationConfigRuleInput{ - OrganizationConfigRuleName: aws.String(d.Id()), + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Organization Managed Rule (%s): %s", d.Id(), err) } - _, err := conn.DeleteOrganizationConfigRuleWithContext(ctx, input) + if _, err := waitOrganizationConfigRuleDeleted(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { + return sdkdiag.AppendErrorf(diags, "waiting for ConfigService Organization Managed Rule (%s) delete: %s", d.Id(), err) + } + + return diags +} + +func findOrganizationManagedRuleByName(ctx context.Context, conn *configservice.Client, name string) (*types.OrganizationConfigRule, error) { + output, err := findOrganizationConfigRuleByName(ctx, conn, name) if err != nil { - return sdkdiag.AppendErrorf(diags, "deleting Config Organization Managed Rule (%s): %s", d.Id(), err) + return nil, err } - if err := waitForOrganizationRuleStatusDeleteSuccessful(ctx, conn, d.Id(), d.Timeout(schema.TimeoutDelete)); err != nil { - return sdkdiag.AppendErrorf(diags, "waiting for Config Organization Managed Rule (%s) deletion: %s", d.Id(), err) + if output.OrganizationManagedRuleMetadata == nil { + return nil, tfresource.NewEmptyResultError(nil) } - return diags + return output, nil +} + +func findOrganizationConfigRuleByName(ctx context.Context, conn *configservice.Client, name string) (*types.OrganizationConfigRule, error) { + input := &configservice.DescribeOrganizationConfigRulesInput{ + OrganizationConfigRuleNames: []string{name}, + } + + return findOrganizationConfigRule(ctx, conn, input) +} + +func findOrganizationConfigRule(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConfigRulesInput) (*types.OrganizationConfigRule, error) { + output, err := findOrganizationConfigRules(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findOrganizationConfigRules(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConfigRulesInput) ([]types.OrganizationConfigRule, error) { + var output []types.OrganizationConfigRule + + pages := configservice.NewDescribeOrganizationConfigRulesPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchOrganizationConfigRuleException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if errs.IsAErrorMessageContains[*types.OrganizationAccessDeniedException](err, "This action can only be made by accounts in an AWS Organization") { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.OrganizationConfigRules...) + } + + return output, nil +} + +func findOrganizationConfigRuleStatusByName(ctx context.Context, conn *configservice.Client, name string) (*types.OrganizationConfigRuleStatus, error) { + input := &configservice.DescribeOrganizationConfigRuleStatusesInput{ + OrganizationConfigRuleNames: []string{name}, + } + + output, err := findOrganizationConfigRuleStatus(ctx, conn, input) + + if err != nil { + return nil, err + } + + if status := output.OrganizationRuleStatus; status == types.OrganizationRuleStatusDeleteSuccessful { + return nil, &retry.NotFoundError{ + Message: string(status), + LastRequest: input, + } + } + + return output, nil +} + +func findOrganizationConfigRuleStatus(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConfigRuleStatusesInput) (*types.OrganizationConfigRuleStatus, error) { + output, err := findOrganizationConfigRuleStatuses(ctx, conn, input) + + if err != nil { + return nil, err + } + + return tfresource.AssertSingleValueResult(output) +} + +func findOrganizationConfigRuleStatuses(ctx context.Context, conn *configservice.Client, input *configservice.DescribeOrganizationConfigRuleStatusesInput) ([]types.OrganizationConfigRuleStatus, error) { + var output []types.OrganizationConfigRuleStatus + + pages := configservice.NewDescribeOrganizationConfigRuleStatusesPaginator(conn, input) + for pages.HasMorePages() { + const ( + timeout = 15 * time.Second + ) + outputRaw, err := tfresource.RetryWhenIsA[*types.OrganizationAccessDeniedException](ctx, timeout, func() (interface{}, error) { + return pages.NextPage(ctx) + }) + + if errs.IsA[*types.NoSuchOrganizationConfigRuleException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, outputRaw.(*configservice.DescribeOrganizationConfigRuleStatusesOutput).OrganizationConfigRuleStatuses...) + } + + return output, nil +} + +func findOrganizationConfigRuleDetailedStatusesByTwoPartKey(ctx context.Context, conn *configservice.Client, name string, status types.MemberAccountRuleStatus) ([]types.MemberAccountStatus, error) { + input := &configservice.GetOrganizationConfigRuleDetailedStatusInput{ + Filters: &types.StatusDetailFilters{ + MemberAccountRuleStatus: status, + }, + OrganizationConfigRuleName: aws.String(name), + } + + return findOrganizationConfigRuleDetailedStatuses(ctx, conn, input) +} + +func findOrganizationConfigRuleDetailedStatuses(ctx context.Context, conn *configservice.Client, input *configservice.GetOrganizationConfigRuleDetailedStatusInput) ([]types.MemberAccountStatus, error) { + var output []types.MemberAccountStatus + + pages := configservice.NewGetOrganizationConfigRuleDetailedStatusPaginator(conn, input) + for pages.HasMorePages() { + page, err := pages.NextPage(ctx) + + if errs.IsA[*types.NoSuchOrganizationConfigRuleException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } + } + + if err != nil { + return nil, err + } + + output = append(output, page.OrganizationConfigRuleDetailedStatus...) + } + + return output, nil +} + +func statusOrganizationConfigRule(ctx context.Context, conn *configservice.Client, name string) retry.StateRefreshFunc { + return func() (interface{}, string, error) { + output, err := findOrganizationConfigRuleStatusByName(ctx, conn, name) + + if tfresource.NotFound(err) { + return nil, "", nil + } + + if err != nil { + return nil, "", err + } + + return output, string(output.OrganizationRuleStatus), err + } +} + +func waitOrganizationConfigRuleCreated(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.OrganizationConfigRuleStatus, error) { //nolint:unparam + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.OrganizationRuleStatusCreateInProgress), + Target: enum.Slice(types.OrganizationRuleStatusCreateSuccessful), + Refresh: statusOrganizationConfigRule(ctx, conn, name), + Timeout: timeout, + Delay: 30 * time.Second, + NotFoundChecks: 10, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.OrganizationConfigRuleStatus); ok { + tfresource.SetLastError(err, organizationConfigRuleStatusError(ctx, conn, output)) + + return output, err + } + + return nil, err +} + +func waitOrganizationConfigRuleUpdated(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.OrganizationConfigRuleStatus, error) { //nolint:unparam + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.OrganizationRuleStatusUpdateInProgress), + Target: enum.Slice(types.OrganizationRuleStatusUpdateSuccessful), + Refresh: statusOrganizationConfigRule(ctx, conn, name), + Timeout: timeout, + Delay: 10 * time.Second, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.OrganizationConfigRuleStatus); ok { + tfresource.SetLastError(err, organizationConfigRuleStatusError(ctx, conn, output)) + + return output, err + } + + return nil, err +} + +func waitOrganizationConfigRuleDeleted(ctx context.Context, conn *configservice.Client, name string, timeout time.Duration) (*types.OrganizationConfigRuleStatus, error) { //nolint:unparam + stateConf := &retry.StateChangeConf{ + Pending: enum.Slice(types.OrganizationRuleStatusDeleteInProgress), + Target: []string{}, + Refresh: statusOrganizationConfigRule(ctx, conn, name), + Timeout: timeout, + Delay: 10 * time.Second, + ContinuousTargetOccurence: 2, + } + + outputRaw, err := stateConf.WaitForStateContext(ctx) + + if output, ok := outputRaw.(*types.OrganizationConfigRuleStatus); ok { + tfresource.SetLastError(err, organizationConfigRuleStatusError(ctx, conn, output)) + + return output, err + } + + return nil, err +} + +func organizationConfigRuleStatusError(ctx context.Context, conn *configservice.Client, apiObject *types.OrganizationConfigRuleStatus) error { + errs := []error{fmt.Errorf("%s: %s", aws.ToString(apiObject.ErrorCode), aws.ToString(apiObject.ErrorMessage))} + + var detailedStatus types.MemberAccountRuleStatus + switch apiObject.OrganizationRuleStatus { + case types.OrganizationRuleStatusCreateFailed: + detailedStatus = types.MemberAccountRuleStatusCreateFailed + case types.OrganizationRuleStatusUpdateFailed: + detailedStatus = types.MemberAccountRuleStatusUpdateFailed + case types.OrganizationRuleStatusDeleteFailed: + detailedStatus = types.MemberAccountRuleStatusDeleteFailed + } + + if detailedStatus != "" { + if v, err := findOrganizationConfigRuleDetailedStatusesByTwoPartKey(ctx, conn, aws.ToString(apiObject.OrganizationConfigRuleName), detailedStatus); err == nil { + for _, v := range v { + err := fmt.Errorf("%s: %s", aws.ToString(v.ErrorCode), aws.ToString(v.ErrorMessage)) + errs = append(errs, fmt.Errorf("Account ID (%s): %w", aws.ToString(v.AccountId), err)) + } + } + } + + return errors.Join(errs...) } diff --git a/internal/service/configservice/organization_managed_rule_test.go b/internal/service/configservice/organization_managed_rule_test.go index 293a98ad710..372c38c2c3c 100644 --- a/internal/service/configservice/organization_managed_rule_test.go +++ b/internal/service/configservice/organization_managed_rule_test.go @@ -9,20 +9,21 @@ import ( "testing" "github.com/YakDriver/regexache" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/errs" tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccOrganizationManagedRule_basic(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -60,7 +61,7 @@ func testAccOrganizationManagedRule_basic(t *testing.T) { func testAccOrganizationManagedRule_disappears(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -102,7 +103,7 @@ func testAccOrganizationManagedRule_errorHandling(t *testing.T) { func testAccOrganizationManagedRule_Description(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -137,7 +138,7 @@ func testAccOrganizationManagedRule_Description(t *testing.T) { func testAccOrganizationManagedRule_ExcludedAccounts(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -172,7 +173,7 @@ func testAccOrganizationManagedRule_ExcludedAccounts(t *testing.T) { func testAccOrganizationManagedRule_InputParameters(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -210,7 +211,7 @@ func testAccOrganizationManagedRule_InputParameters(t *testing.T) { func testAccOrganizationManagedRule_MaximumExecutionFrequency(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -245,7 +246,7 @@ func testAccOrganizationManagedRule_MaximumExecutionFrequency(t *testing.T) { func testAccOrganizationManagedRule_ResourceIdScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -280,7 +281,7 @@ func testAccOrganizationManagedRule_ResourceIdScope(t *testing.T) { func testAccOrganizationManagedRule_ResourceTypesScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -315,7 +316,7 @@ func testAccOrganizationManagedRule_ResourceTypesScope(t *testing.T) { func testAccOrganizationManagedRule_RuleIdentifier(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -350,7 +351,7 @@ func testAccOrganizationManagedRule_RuleIdentifier(t *testing.T) { func testAccOrganizationManagedRule_TagKeyScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -385,7 +386,7 @@ func testAccOrganizationManagedRule_TagKeyScope(t *testing.T) { func testAccOrganizationManagedRule_TagValueScope(t *testing.T) { ctx := acctest.Context(t) - var rule configservice.OrganizationConfigRule + var rule types.OrganizationConfigRule rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_config_organization_managed_rule.test" @@ -418,26 +419,22 @@ func testAccOrganizationManagedRule_TagValueScope(t *testing.T) { }) } -func testAccCheckOrganizationManagedRuleExists(ctx context.Context, resourceName string, ocr *configservice.OrganizationConfigRule) resource.TestCheckFunc { +func testAccCheckOrganizationManagedRuleExists(ctx context.Context, n string, v *types.OrganizationConfigRule) 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) } - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) - rule, err := tfconfig.DescribeOrganizationConfigRule(ctx, conn, rs.Primary.ID) + output, err := tfconfig.FindOrganizationManagedRuleByName(ctx, conn, rs.Primary.ID) if err != nil { - return fmt.Errorf("error describing Config Organization Managed Rule (%s): %s", rs.Primary.ID, err) + return err } - if rule == nil { - return fmt.Errorf("Config Organization Managed Rule (%s) not found", rs.Primary.ID) - } - - *ocr = *rule + *v = *output return nil } @@ -445,26 +442,24 @@ func testAccCheckOrganizationManagedRuleExists(ctx context.Context, resourceName func testAccCheckOrganizationManagedRuleDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_organization_managed_rule" { continue } - rule, err := tfconfig.DescribeOrganizationConfigRule(ctx, conn, rs.Primary.ID) + _, err := tfconfig.FindOrganizationManagedRuleByName(ctx, conn, rs.Primary.ID) - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchOrganizationConfigRuleException) { + if tfresource.NotFound(err) || errs.IsA[*types.OrganizationAccessDeniedException](err) { continue } if err != nil { - return fmt.Errorf("error describing Config Organization Managed Rule (%s): %s", rs.Primary.ID, err) + return err } - if rule != nil { - return fmt.Errorf("Config Organization Managed Rule (%s) still exists", rs.Primary.ID) - } + return fmt.Errorf("ConfigService Organization Managed Rule %s still exists", rs.Primary.ID) } return nil diff --git a/internal/service/configservice/remediation_configuration.go b/internal/service/configservice/remediation_configuration.go index 513ea019011..705fb30d6c3 100644 --- a/internal/service/configservice/remediation_configuration.go +++ b/internal/service/configservice/remediation_configuration.go @@ -5,33 +5,27 @@ package configservice import ( "context" - "errors" - "fmt" "log" "slices" "time" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" "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/enum" + "github.com/hashicorp/terraform-provider-aws/internal/errs" + "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/names" ) -const ( - // Maximum amount of time to wait for Config service eventual consistency on deletion - remediationConfigurationDeletionTimeout = 2 * time.Minute -) - -// @SDKResource("aws_config_remediation_configuration") -func ResourceRemediationConfiguration() *schema.Resource { +// @SDKResource("aws_config_remediation_configuration", name="Remediation Configuration") +func resourceRemediationConfiguration() *schema.Resource { return &schema.Resource{ CreateWithoutTimeout: resourceRemediationConfigurationPut, ReadWithoutTimeout: resourceRemediationConfigurationRead, @@ -133,9 +127,9 @@ func ResourceRemediationConfiguration() *schema.Resource { ValidateFunc: validation.StringLenBetween(1, 256), }, "target_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice(configservice.RemediationTargetType_Values(), false), + Type: schema.TypeString, + Required: true, + ValidateDiagFunc: enum.Validate[types.RemediationTargetType](), }, "target_version": { Type: schema.TypeString, @@ -147,185 +141,199 @@ func ResourceRemediationConfiguration() *schema.Resource { func resourceRemediationConfigurationPut(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) name := d.Get("config_rule_name").(string) - input := configservice.RemediationConfiguration{ + remediationConfiguration := types.RemediationConfiguration{ ConfigRuleName: aws.String(name), } + if v, ok := d.GetOk("automatic"); ok { + remediationConfiguration.Automatic = v.(bool) + } + + if v, ok := d.GetOk("execution_controls"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { + remediationConfiguration.ExecutionControls = expandExecutionControls(v.([]interface{})[0].(map[string]interface{})) + } + + if v, ok := d.GetOk("maximum_automatic_attempts"); ok { + remediationConfiguration.MaximumAutomaticAttempts = aws.Int32(int32(v.(int))) + } + if v, ok := d.GetOk("parameter"); ok && len(v.([]interface{})) > 0 { - input.Parameters = expandRemediationParameterValues(v.([]interface{})) + remediationConfiguration.Parameters = expandRemediationParameterValues(v.([]interface{})) } + if v, ok := d.GetOk("resource_type"); ok { - input.ResourceType = aws.String(v.(string)) + remediationConfiguration.ResourceType = aws.String(v.(string)) + } + + if v, ok := d.GetOk("retry_attempt_seconds"); ok { + remediationConfiguration.RetryAttemptSeconds = aws.Int64(int64(v.(int))) } + if v, ok := d.GetOk("target_id"); ok { - input.TargetId = aws.String(v.(string)) + remediationConfiguration.TargetId = aws.String(v.(string)) } + if v, ok := d.GetOk("target_type"); ok { - input.TargetType = aws.String(v.(string)) + remediationConfiguration.TargetType = types.RemediationTargetType(v.(string)) } + if v, ok := d.GetOk("target_version"); ok { - input.TargetVersion = aws.String(v.(string)) - } - if v, ok := d.GetOk("automatic"); ok { - input.Automatic = aws.Bool(v.(bool)) - } - if v, ok := d.GetOk("maximum_automatic_attempts"); ok { - input.MaximumAutomaticAttempts = aws.Int64(int64(v.(int))) - } - if v, ok := d.GetOk("retry_attempt_seconds"); ok { - input.RetryAttemptSeconds = aws.Int64(int64(v.(int))) - } - if v, ok := d.GetOk("execution_controls"); ok && len(v.([]interface{})) > 0 && v.([]interface{})[0] != nil { - input.ExecutionControls = expandExecutionControls(v.([]interface{})[0].(map[string]interface{})) + remediationConfiguration.TargetVersion = aws.String(v.(string)) } - inputs := configservice.PutRemediationConfigurationsInput{ - RemediationConfigurations: []*configservice.RemediationConfiguration{&input}, - } + _, err := conn.PutRemediationConfigurations(ctx, &configservice.PutRemediationConfigurationsInput{ + RemediationConfigurations: []types.RemediationConfiguration{remediationConfiguration}, + }) - log.Printf("[DEBUG] Creating AWSConfig remediation configuration: %s", inputs) - _, err := conn.PutRemediationConfigurationsWithContext(ctx, &inputs) if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionCreating, ResNameRemediationConfiguration, fmt.Sprintf("%+v", inputs), err) + return sdkdiag.AppendErrorf(diags, "putting ConfigService Remediation Configuration (%s): %s", name, err) } - d.SetId(name) - - log.Printf("[DEBUG] AWSConfig config remediation configuration for rule %q created", name) + if d.IsNewResource() { + d.SetId(name) + } return append(diags, resourceRemediationConfigurationRead(ctx, d, meta)...) } func resourceRemediationConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) - out, err := conn.DescribeRemediationConfigurationsWithContext(ctx, &configservice.DescribeRemediationConfigurationsInput{ - ConfigRuleNames: []*string{aws.String(d.Id())}, - }) - - if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, configservice.ErrCodeNoSuchConfigRuleException) { - log.Printf("[WARN] Config Rule %q is gone (NoSuchConfigRuleException)", d.Id()) - d.SetId("") - return diags - } + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) - if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameRemediationConfiguration, d.Id(), err) - } + remediationConfiguration, err := findRemediationConfigurationByConfigRuleName(ctx, conn, d.Id()) - numberOfRemediationConfigurations := len(out.RemediationConfigurations) - if !d.IsNewResource() && numberOfRemediationConfigurations < 1 { - log.Printf("[WARN] No Remediation Configuration for Config Rule %q (no remediation configuration found)", d.Id()) + if !d.IsNewResource() && tfresource.NotFound(err) { + log.Printf("[WARN] ConfigService Remediation Configuration (%s) not found, removing from state", d.Id()) d.SetId("") return diags } - if d.IsNewResource() && numberOfRemediationConfigurations < 1 { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameRemediationConfiguration, d.Id(), errors.New("none found after creation")) + if err != nil { + return sdkdiag.AppendErrorf(diags, "reading ConfigService Remediation Configuration (%s): %s", d.Id(), err) } - log.Printf("[DEBUG] AWS Config remediation configurations received: %s", out) - - remediationConfiguration := out.RemediationConfigurations[0] d.Set("arn", remediationConfiguration.Arn) - d.Set("config_rule_name", remediationConfiguration.ConfigRuleName) - d.Set("resource_type", remediationConfiguration.ResourceType) - d.Set("target_id", remediationConfiguration.TargetId) - d.Set("target_type", remediationConfiguration.TargetType) - d.Set("target_version", remediationConfiguration.TargetVersion) d.Set("automatic", remediationConfiguration.Automatic) - d.Set("maximum_automatic_attempts", remediationConfiguration.MaximumAutomaticAttempts) - d.Set("retry_attempt_seconds", remediationConfiguration.RetryAttemptSeconds) - d.Set("maximum_automatic_attempts", remediationConfiguration.MaximumAutomaticAttempts) - + d.Set("config_rule_name", remediationConfiguration.ConfigRuleName) if err := d.Set("execution_controls", flattenExecutionControls(remediationConfiguration.ExecutionControls)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameRemediationConfiguration, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting execution_controls: %s", err) } - + d.Set("maximum_automatic_attempts", remediationConfiguration.MaximumAutomaticAttempts) if err := d.Set("parameter", flattenRemediationParameterValues(remediationConfiguration.Parameters)); err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionReading, ResNameRemediationConfiguration, d.Id(), err) + return sdkdiag.AppendErrorf(diags, "setting parameter: %s", err) } + d.Set("resource_type", remediationConfiguration.ResourceType) + d.Set("retry_attempt_seconds", remediationConfiguration.RetryAttemptSeconds) + d.Set("target_id", remediationConfiguration.TargetId) + d.Set("target_type", remediationConfiguration.TargetType) + d.Set("target_version", remediationConfiguration.TargetVersion) return diags } func resourceRemediationConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - conn := meta.(*conns.AWSClient).ConfigServiceConn(ctx) - - name := d.Get("config_rule_name").(string) + conn := meta.(*conns.AWSClient).ConfigServiceClient(ctx) input := &configservice.DeleteRemediationConfigurationInput{ - ConfigRuleName: aws.String(name), + ConfigRuleName: aws.String(d.Id()), } if v, ok := d.GetOk("resource_type"); ok { input.ResourceType = aws.String(v.(string)) } - log.Printf("[DEBUG] Deleting AWS Config remediation configurations for rule %q", name) - err := retry.RetryContext(ctx, remediationConfigurationDeletionTimeout, func() *retry.RetryError { - _, err := conn.DeleteRemediationConfigurationWithContext(ctx, input) + const ( + timeout = 2 * time.Minute + ) + log.Printf("[DEBUG] Deleting ConfigService Remediation Configuration: %s", d.Id()) + _, err := tfresource.RetryWhenIsA[*types.ResourceInUseException](ctx, timeout, func() (interface{}, error) { + return conn.DeleteRemediationConfiguration(ctx, input) + }) - if tfawserr.ErrCodeEquals(err, configservice.ErrCodeResourceInUseException) { - return retry.RetryableError(err) - } + if errs.IsA[*types.NoSuchRemediationConfigurationException](err) { + return diags + } - if err != nil { - return retry.NonRetryableError(err) - } + if err != nil { + return sdkdiag.AppendErrorf(diags, "deleting ConfigService Remediation Configuration (%s): %s", d.Id(), err) + } - return nil - }) + return diags +} - if tfresource.TimedOut(err) { - _, err = conn.DeleteRemediationConfigurationWithContext(ctx, input) +func findRemediationConfigurationByConfigRuleName(ctx context.Context, conn *configservice.Client, name string) (*types.RemediationConfiguration, error) { + input := &configservice.DescribeRemediationConfigurationsInput{ + ConfigRuleNames: []string{name}, } + return findRemediationConfiguration(ctx, conn, input) +} + +func findRemediationConfiguration(ctx context.Context, conn *configservice.Client, input *configservice.DescribeRemediationConfigurationsInput) (*types.RemediationConfiguration, error) { + output, err := findRemediationConfigurations(ctx, conn, input) + if err != nil { - return create.AppendDiagError(diags, names.ConfigService, create.ErrActionDeleting, ResNameRemediationConfiguration, d.Id(), err) + return nil, err } - return diags + return tfresource.AssertSingleValueResult(output) } -func expandRemediationParameterValue(tfMap map[string]interface{}) *configservice.RemediationParameterValue { - if tfMap == nil { - return nil +func findRemediationConfigurations(ctx context.Context, conn *configservice.Client, input *configservice.DescribeRemediationConfigurationsInput) ([]types.RemediationConfiguration, error) { + output, err := conn.DescribeRemediationConfigurations(ctx, input) + + if errs.IsA[*types.NoSuchConfigRuleException](err) { + return nil, &retry.NotFoundError{ + LastError: err, + LastRequest: input, + } } - apiObject := &configservice.RemediationParameterValue{} + if err != nil { + return nil, err + } + + if output == nil { + return nil, tfresource.NewEmptyResultError(input) + } + + return output.RemediationConfigurations, nil +} + +func expandRemediationParameterValue(tfMap map[string]interface{}) types.RemediationParameterValue { + apiObject := types.RemediationParameterValue{} if v, ok := tfMap["resource_value"].(string); ok && v != "" { - apiObject.ResourceValue = &configservice.ResourceValue{ - Value: aws.String(v), + apiObject.ResourceValue = &types.ResourceValue{ + Value: types.ResourceValueType(v), } } if v, ok := tfMap["static_value"].(string); ok && v != "" { - apiObject.StaticValue = &configservice.StaticValue{ - Values: aws.StringSlice([]string{v}), + apiObject.StaticValue = &types.StaticValue{ + Values: []string{v}, } } if v, ok := tfMap["static_values"].([]interface{}); ok && len(v) > 0 && v[0] != nil { - apiObject.StaticValue = &configservice.StaticValue{ - Values: flex.ExpandStringList(v), + apiObject.StaticValue = &types.StaticValue{ + Values: flex.ExpandStringValueList(v), } } return apiObject } -func expandRemediationParameterValues(tfList []interface{}) map[string]*configservice.RemediationParameterValue { +func expandRemediationParameterValues(tfList []interface{}) map[string]types.RemediationParameterValue { if len(tfList) == 0 { return nil } - apiObjects := make(map[string]*configservice.RemediationParameterValue) + apiObjects := make(map[string]types.RemediationParameterValue) for _, tfMapRaw := range tfList { tfMap, ok := tfMapRaw.(map[string]interface{}) @@ -344,30 +352,30 @@ func expandRemediationParameterValues(tfList []interface{}) map[string]*configse return apiObjects } -func expandSSMControls(tfMap map[string]interface{}) *configservice.SsmControls { +func expandSSMControls(tfMap map[string]interface{}) *types.SsmControls { if tfMap == nil { return nil } - apiObject := &configservice.SsmControls{} + apiObject := &types.SsmControls{} if v, ok := tfMap["concurrent_execution_rate_percentage"].(int); ok && v != 0 { - apiObject.ConcurrentExecutionRatePercentage = aws.Int64(int64(v)) + apiObject.ConcurrentExecutionRatePercentage = aws.Int32(int32(v)) } if v, ok := tfMap["error_percentage"].(int); ok && v != 0 { - apiObject.ErrorPercentage = aws.Int64(int64(v)) + apiObject.ErrorPercentage = aws.Int32(int32(v)) } return apiObject } -func expandExecutionControls(tfMap map[string]interface{}) *configservice.ExecutionControls { +func expandExecutionControls(tfMap map[string]interface{}) *types.ExecutionControls { if tfMap == nil { return nil } - apiObject := &configservice.ExecutionControls{} + apiObject := &types.ExecutionControls{} if v, ok := tfMap["ssm_controls"].([]interface{}); ok && len(v) > 0 && v[0] != nil { apiObject.SsmControls = expandSSMControls(v[0].(map[string]interface{})) @@ -376,29 +384,32 @@ func expandExecutionControls(tfMap map[string]interface{}) *configservice.Execut return apiObject } -func flattenRemediationParameterValues(parameters map[string]*configservice.RemediationParameterValue) []interface{} { - var items []interface{} +func flattenRemediationParameterValues(apiObjects map[string]types.RemediationParameterValue) []interface{} { + var tfList []interface{} + + for key, value := range apiObjects { + tfMap := map[string]interface{}{ + "name": key, + } - for key, value := range parameters { - item := make(map[string]interface{}) - item["name"] = key if v := value.ResourceValue; v != nil { - item["resource_value"] = aws.StringValue(v.Value) + tfMap["resource_value"] = v.Value } + if v := value.StaticValue; v != nil { if len(v.Values) == 1 { - item["static_value"] = aws.StringValue(v.Values[0]) + tfMap["static_value"] = v.Values[0] } else if len(v.Values) > 1 { - item["static_values"] = aws.StringValueSlice(v.Values) + tfMap["static_values"] = v.Values } } else { - item["static_values"] = make([]interface{}, 0) + tfMap["static_values"] = make([]interface{}, 0) } - items = append(items, item) + tfList = append(tfList, tfMap) } - slices.SortFunc(items, func(a, b interface{}) int { + slices.SortFunc(tfList, func(a, b interface{}) int { if a.(map[string]interface{})["name"].(string) < b.(map[string]interface{})["name"].(string) { return -1 } @@ -410,28 +421,32 @@ func flattenRemediationParameterValues(parameters map[string]*configservice.Reme return 0 }) - return items + return tfList } -func flattenExecutionControls(controls *configservice.ExecutionControls) []interface{} { - if controls == nil { +func flattenExecutionControls(apiObject *types.ExecutionControls) []interface{} { + if apiObject == nil { return nil } + return []interface{}{map[string]interface{}{ - "ssm_controls": flattenSSMControls(controls.SsmControls), + "ssm_controls": flattenSSMControls(apiObject.SsmControls), }} } -func flattenSSMControls(controls *configservice.SsmControls) []interface{} { - if controls == nil { +func flattenSSMControls(apiObject *types.SsmControls) []interface{} { + if apiObject == nil { return nil } - m := make(map[string]interface{}) - if controls.ConcurrentExecutionRatePercentage != nil { - m["concurrent_execution_rate_percentage"] = controls.ConcurrentExecutionRatePercentage + + tfMap := map[string]interface{}{} + + if apiObject.ConcurrentExecutionRatePercentage != nil { + tfMap["concurrent_execution_rate_percentage"] = apiObject.ConcurrentExecutionRatePercentage } - if controls.ErrorPercentage != nil { - m["error_percentage"] = controls.ErrorPercentage + + if apiObject.ErrorPercentage != nil { + tfMap["error_percentage"] = apiObject.ErrorPercentage } - return []interface{}{m} + return []interface{}{tfMap} } diff --git a/internal/service/configservice/remediation_configuration_test.go b/internal/service/configservice/remediation_configuration_test.go index 1bdb6114593..2257797f906 100644 --- a/internal/service/configservice/remediation_configuration_test.go +++ b/internal/service/configservice/remediation_configuration_test.go @@ -10,31 +10,29 @@ import ( "strconv" "testing" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/service/configservice" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/configservice/types" sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" "github.com/hashicorp/terraform-provider-aws/internal/conns" - "github.com/hashicorp/terraform-provider-aws/internal/create" - tfconfigservice "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + tfconfig "github.com/hashicorp/terraform-provider-aws/internal/service/configservice" + "github.com/hashicorp/terraform-provider-aws/internal/tfresource" "github.com/hashicorp/terraform-provider-aws/names" ) func testAccRemediationConfiguration_basic(t *testing.T) { ctx := acctest.Context(t) - var rc configservice.RemediationConfiguration + var rc types.RemediationConfiguration resourceName := "aws_config_remediation_configuration.test" - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) automatic := "false" rAttempts := sdkacctest.RandIntRange(1, 25) rSeconds := sdkacctest.RandIntRange(1, 2678000) rExecPct := sdkacctest.RandIntRange(1, 100) rErrorPct := sdkacctest.RandIntRange(1, 100) - prefix := "Original" sseAlgorithm := "AES256" - expectedName := fmt.Sprintf("%s-tf-acc-test-%d", prefix, rInt) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -43,10 +41,10 @@ func testAccRemediationConfiguration_basic(t *testing.T) { CheckDestroy: testAccCheckRemediationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccRemediationConfigurationConfig_basic(prefix, sseAlgorithm, rInt, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), + Config: testAccRemediationConfigurationConfig_basic(rName, sseAlgorithm, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), Check: resource.ComposeTestCheckFunc( testAccCheckRemediationConfigurationExists(ctx, resourceName, &rc), - resource.TestCheckResourceAttr(resourceName, "config_rule_name", expectedName), + resource.TestCheckResourceAttr(resourceName, "config_rule_name", rName), resource.TestCheckResourceAttr(resourceName, "target_id", "AWS-EnableS3BucketEncryption"), resource.TestCheckResourceAttr(resourceName, "target_type", "SSM_DOCUMENT"), resource.TestCheckResourceAttr(resourceName, "parameter.#", "3"), @@ -71,12 +69,10 @@ func testAccRemediationConfiguration_basic(t *testing.T) { func testAccRemediationConfiguration_basicBackwardCompatible(t *testing.T) { ctx := acctest.Context(t) - var rc configservice.RemediationConfiguration + var rc types.RemediationConfiguration resourceName := "aws_config_remediation_configuration.test" - rInt := sdkacctest.RandInt() - prefix := "Original" + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) sseAlgorithm := "AES256" - expectedName := fmt.Sprintf("%s-tf-acc-test-%d", prefix, rInt) resource.Test(t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, @@ -85,10 +81,10 @@ func testAccRemediationConfiguration_basicBackwardCompatible(t *testing.T) { CheckDestroy: testAccCheckRemediationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccRemediationConfigurationConfig_olderSchema(prefix, sseAlgorithm, rInt), + Config: testAccRemediationConfigurationConfig_olderSchema(rName, sseAlgorithm), Check: resource.ComposeTestCheckFunc( testAccCheckRemediationConfigurationExists(ctx, resourceName, &rc), - resource.TestCheckResourceAttr(resourceName, "config_rule_name", expectedName), + resource.TestCheckResourceAttr(resourceName, "config_rule_name", rName), resource.TestCheckResourceAttr(resourceName, "target_id", "AWS-EnableS3BucketEncryption"), resource.TestCheckResourceAttr(resourceName, "target_type", "SSM_DOCUMENT"), resource.TestCheckResourceAttr(resourceName, "parameter.#", "3"), @@ -105,15 +101,14 @@ func testAccRemediationConfiguration_basicBackwardCompatible(t *testing.T) { func testAccRemediationConfiguration_disappears(t *testing.T) { ctx := acctest.Context(t) - var rc configservice.RemediationConfiguration + var rc types.RemediationConfiguration resourceName := "aws_config_remediation_configuration.test" - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) automatic := "false" rAttempts := sdkacctest.RandIntRange(1, 25) rSeconds := sdkacctest.RandIntRange(1, 2678000) rExecPct := sdkacctest.RandIntRange(1, 100) rErrorPct := sdkacctest.RandIntRange(1, 100) - prefix := "original" sseAlgorithm := "AES256" resource.Test(t, resource.TestCase{ @@ -123,10 +118,10 @@ func testAccRemediationConfiguration_disappears(t *testing.T) { CheckDestroy: testAccCheckRemediationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccRemediationConfigurationConfig_basic(prefix, sseAlgorithm, rInt, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), + Config: testAccRemediationConfigurationConfig_basic(rName, sseAlgorithm, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), Check: resource.ComposeTestCheckFunc( testAccCheckRemediationConfigurationExists(ctx, resourceName, &rc), - acctest.CheckResourceDisappears(ctx, acctest.Provider, tfconfigservice.ResourceRemediationConfiguration(), resourceName), + acctest.CheckResourceDisappears(ctx, acctest.Provider, tfconfig.ResourceRemediationConfiguration(), resourceName), ), ExpectNonEmptyPlan: true, }, @@ -134,53 +129,12 @@ func testAccRemediationConfiguration_disappears(t *testing.T) { }) } -func testAccRemediationConfiguration_recreates(t *testing.T) { - ctx := acctest.Context(t) - var original configservice.RemediationConfiguration - var updated configservice.RemediationConfiguration - resourceName := "aws_config_remediation_configuration.test" - rInt := sdkacctest.RandInt() - automatic := "false" - rAttempts := sdkacctest.RandIntRange(1, 25) - rSeconds := sdkacctest.RandIntRange(1, 2678000) - rExecPct := sdkacctest.RandIntRange(1, 100) - rErrorPct := sdkacctest.RandIntRange(1, 100) - - originalName := "Original" - updatedName := "Updated" - sseAlgorithm := "AES256" - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - CheckDestroy: testAccCheckRemediationConfigurationDestroy(ctx), - Steps: []resource.TestStep{ - { - Config: testAccRemediationConfigurationConfig_basic(originalName, sseAlgorithm, rInt, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), - Check: resource.ComposeTestCheckFunc( - testAccCheckRemediationConfigurationExists(ctx, resourceName, &original), - resource.TestCheckResourceAttr(resourceName, "config_rule_name", fmt.Sprintf("%s-tf-acc-test-%d", originalName, rInt)), - ), - }, - { - Config: testAccRemediationConfigurationConfig_basic(updatedName, sseAlgorithm, rInt, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), - Check: resource.ComposeTestCheckFunc( - testAccCheckRemediationConfigurationExists(ctx, resourceName, &updated), - testAccCheckRemediationConfigurationRecreated(&original, &updated), - resource.TestCheckResourceAttr(resourceName, "config_rule_name", fmt.Sprintf("%s-tf-acc-test-%d", updatedName, rInt)), - ), - }, - }, - }) -} - func testAccRemediationConfiguration_updates(t *testing.T) { ctx := acctest.Context(t) - var original configservice.RemediationConfiguration - var updated configservice.RemediationConfiguration + var original types.RemediationConfiguration + var updated types.RemediationConfiguration resourceName := "aws_config_remediation_configuration.test" - rInt := sdkacctest.RandInt() + rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) automatic := "false" rAttempts := sdkacctest.RandIntRange(1, 25) rSeconds := sdkacctest.RandIntRange(1, 2678000) @@ -191,8 +145,6 @@ func testAccRemediationConfiguration_updates(t *testing.T) { uSeconds := sdkacctest.RandIntRange(1, 2678000) uExecPct := sdkacctest.RandIntRange(1, 100) uErrorPct := sdkacctest.RandIntRange(1, 100) - - name := "Original" originalSseAlgorithm := "AES256" updatedSseAlgorithm := "aws:kms" @@ -203,7 +155,7 @@ func testAccRemediationConfiguration_updates(t *testing.T) { CheckDestroy: testAccCheckRemediationConfigurationDestroy(ctx), Steps: []resource.TestStep{ { - Config: testAccRemediationConfigurationConfig_basic(name, originalSseAlgorithm, rInt, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), + Config: testAccRemediationConfigurationConfig_basic(rName, originalSseAlgorithm, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), Check: resource.ComposeTestCheckFunc( testAccCheckRemediationConfigurationExists(ctx, resourceName, &original), resource.TestCheckResourceAttr(resourceName, "parameter.2.static_value", originalSseAlgorithm), @@ -218,7 +170,7 @@ func testAccRemediationConfiguration_updates(t *testing.T) { ), }, { - Config: testAccRemediationConfigurationConfig_basic(name, updatedSseAlgorithm, rInt, uAttempts, uSeconds, uExecPct, uErrorPct, uAutomatic), + Config: testAccRemediationConfigurationConfig_basic(rName, updatedSseAlgorithm, uAttempts, uSeconds, uExecPct, uErrorPct, uAutomatic), Check: resource.ComposeTestCheckFunc( testAccCheckRemediationConfigurationExists(ctx, resourceName, &updated), testAccCheckRemediationConfigurationNotRecreated(&original, &updated), @@ -239,7 +191,7 @@ func testAccRemediationConfiguration_updates(t *testing.T) { func testAccRemediationConfiguration_values(t *testing.T) { ctx := acctest.Context(t) - var rc configservice.RemediationConfiguration + var rc types.RemediationConfiguration resourceName := "aws_config_remediation_configuration.test" rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) automatic := "false" @@ -281,71 +233,22 @@ func testAccRemediationConfiguration_values(t *testing.T) { }) } -func testAccRemediationConfiguration_migrateParameters(t *testing.T) { - ctx := acctest.Context(t) - var rc configservice.RemediationConfiguration - resourceName := "aws_config_remediation_configuration.test" - rInt := sdkacctest.RandInt() - automatic := "false" - rAttempts := sdkacctest.RandIntRange(1, 25) - rSeconds := sdkacctest.RandIntRange(1, 2678000) - rExecPct := sdkacctest.RandIntRange(1, 100) - rErrorPct := sdkacctest.RandIntRange(1, 100) - prefix := "Original" - sseAlgorithm := "AES256" - expectedName := fmt.Sprintf("%s-tf-acc-test-%d", prefix, rInt) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { acctest.PreCheck(ctx, t) }, - ErrorCheck: acctest.ErrorCheck(t, names.ConfigServiceServiceID), - CheckDestroy: testAccCheckRemediationConfigurationDestroy(ctx), - Steps: []resource.TestStep{ - { - ExternalProviders: map[string]resource.ExternalProvider{ - "aws": { - Source: "hashicorp/aws", - VersionConstraint: "4.66.0", - }, - }, - Config: testAccRemediationConfigurationConfig_basic(prefix, sseAlgorithm, rInt, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), - Check: resource.ComposeTestCheckFunc( - testAccCheckRemediationConfigurationExists(ctx, resourceName, &rc), - resource.TestCheckResourceAttr(resourceName, "config_rule_name", expectedName), - ), - }, - { - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, - Config: testAccRemediationConfigurationConfig_basic(prefix, sseAlgorithm, rInt, rAttempts, rSeconds, rExecPct, rErrorPct, automatic), - PlanOnly: true, - }, - }, - }) -} - -func testAccCheckRemediationConfigurationExists(ctx context.Context, n string, obj *configservice.RemediationConfiguration) resource.TestCheckFunc { +func testAccCheckRemediationConfigurationExists(ctx context.Context, n string, v *types.RemediationConfiguration) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameRemediationConfiguration, n, errors.New("not found in state")) + return fmt.Errorf("Not found: %s", n) } - if rs.Primary.ID == "" { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameRemediationConfiguration, n, errors.New("ID not set")) - } + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) + + output, err := tfconfig.FindRemediationConfigurationByConfigRuleName(ctx, conn, rs.Primary.ID) - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) - out, err := conn.DescribeRemediationConfigurationsWithContext(ctx, &configservice.DescribeRemediationConfigurationsInput{ - ConfigRuleNames: []*string{aws.String(rs.Primary.Attributes["config_rule_name"])}, - }) if err != nil { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameRemediationConfiguration, n, err) - } - if len(out.RemediationConfigurations) < 1 { - return create.Error(names.ConfigService, create.ErrActionCheckingExistence, tfconfigservice.ResNameRemediationConfiguration, n, errors.New("not found")) + return err } - rc := out.RemediationConfigurations[0] - *obj = *rc + *v = *output return nil } @@ -353,48 +256,40 @@ func testAccCheckRemediationConfigurationExists(ctx context.Context, n string, o func testAccCheckRemediationConfigurationDestroy(ctx context.Context) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).ConfigServiceClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_config_remediation_configuration" { continue } - resp, err := conn.DescribeRemediationConfigurationsWithContext(ctx, &configservice.DescribeRemediationConfigurationsInput{ - ConfigRuleNames: []*string{aws.String(rs.Primary.Attributes["config_rule_name"])}, - }) + _, err := tfconfig.FindRemediationConfigurationByConfigRuleName(ctx, conn, rs.Primary.ID) - if err == nil { - if len(resp.RemediationConfigurations) != 0 && - aws.StringValue(resp.RemediationConfigurations[0].ConfigRuleName) == rs.Primary.Attributes["name"] { - return create.Error(names.ConfigService, create.ErrActionCheckingDestroyed, tfconfigservice.ResNameRemediationConfiguration, rs.Primary.Attributes["name"], errors.New("still exists")) - } + if tfresource.NotFound(err) { + continue } - } - return nil - } -} + if err != nil { + return err + } -func testAccCheckRemediationConfigurationNotRecreated(before, after *configservice.RemediationConfiguration) resource.TestCheckFunc { - return func(s *terraform.State) error { - if aws.StringValue(before.Arn) != aws.StringValue(after.Arn) { - return create.Error(names.ConfigService, create.ErrActionCheckingNotRecreated, tfconfigservice.ResNameRemediationConfiguration, aws.StringValue(before.Arn), fmt.Errorf("ARNs changed, new: %s", aws.StringValue(after.Arn))) + return fmt.Errorf("ConfigService Remediation Configuration %s still exists", rs.Primary.ID) } + return nil } } -func testAccCheckRemediationConfigurationRecreated(before, after *configservice.RemediationConfiguration) resource.TestCheckFunc { +func testAccCheckRemediationConfigurationNotRecreated(before, after *types.RemediationConfiguration) resource.TestCheckFunc { return func(s *terraform.State) error { - if aws.StringValue(before.Arn) == aws.StringValue(after.Arn) { - return create.Error(names.ConfigService, create.ErrActionCheckingRecreated, tfconfigservice.ResNameRemediationConfiguration, aws.StringValue(before.Arn), fmt.Errorf("wasn't recreated, new: %s", aws.StringValue(after.Arn))) + if aws.ToString(before.Arn) != aws.ToString(after.Arn) { + return errors.New("ConfigService Remediation Configuration was recreated") } return nil } } -func testAccRemediationConfigurationConfig_olderSchema(namePrefix, sseAlgorithm string, randInt int) string { +func testAccRemediationConfigurationConfig_olderSchema(rName, sseAlgorithm string) string { return fmt.Sprintf(` resource "aws_config_remediation_configuration" "test" { config_rule_name = aws_config_config_rule.test.name @@ -414,16 +309,16 @@ resource "aws_config_remediation_configuration" "test" { } parameter { name = "SSEAlgorithm" - static_value = "%[2]s" + static_value = %[2]q } } resource "aws_sns_topic" "test" { - name = "sns_topic_name" + name = %[1]q } resource "aws_config_config_rule" "test" { - name = "%[1]s-tf-acc-test-%[3]d" + name = %[1]q source { owner = "AWS" @@ -434,12 +329,12 @@ resource "aws_config_config_rule" "test" { } resource "aws_config_configuration_recorder" "test" { - name = "%[1]s-tf-acc-test-%[3]d" + name = %[1]q role_arn = aws_iam_role.test.arn } resource "aws_iam_role" "test" { - name = "%[1]s-tf-acc-test-awsconfig-%[3]d" + name = %[1]q assume_role_policy = < 0 { return tags @@ -91,7 +91,7 @@ func getTagsIn(ctx context.Context) []*configservice.Tag { } // setTagsOut sets configservice service tags in Context. -func setTagsOut(ctx context.Context, tags []*configservice.Tag) { +func setTagsOut(ctx context.Context, tags []awstypes.Tag) { if inContext, ok := tftags.FromContext(ctx); ok { inContext.TagsOut = option.Some(KeyValueTags(ctx, tags)) } @@ -100,7 +100,7 @@ func setTagsOut(ctx context.Context, tags []*configservice.Tag) { // updateTags updates configservice service tags. // The identifier is typically the Amazon Resource Name (ARN), although // it may also be a different identifier depending on the service. -func updateTags(ctx context.Context, conn configserviceiface.ConfigServiceAPI, identifier string, oldTagsMap, newTagsMap any) error { +func updateTags(ctx context.Context, conn *configservice.Client, identifier string, oldTagsMap, newTagsMap any, optFns ...func(*configservice.Options)) error { oldTags := tftags.New(ctx, oldTagsMap) newTags := tftags.New(ctx, newTagsMap) @@ -111,10 +111,10 @@ func updateTags(ctx context.Context, conn configserviceiface.ConfigServiceAPI, i if len(removedTags) > 0 { input := &configservice.UntagResourceInput{ ResourceArn: aws.String(identifier), - TagKeys: aws.StringSlice(removedTags.Keys()), + TagKeys: removedTags.Keys(), } - _, err := conn.UntagResourceWithContext(ctx, input) + _, err := conn.UntagResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("untagging resource (%s): %w", identifier, err) @@ -129,7 +129,7 @@ func updateTags(ctx context.Context, conn configserviceiface.ConfigServiceAPI, i Tags: Tags(updatedTags), } - _, err := conn.TagResourceWithContext(ctx, input) + _, err := conn.TagResource(ctx, input, optFns...) if err != nil { return fmt.Errorf("tagging resource (%s): %w", identifier, err) @@ -142,5 +142,5 @@ func updateTags(ctx context.Context, conn configserviceiface.ConfigServiceAPI, i // UpdateTags updates configservice service tags. // It is called from outside this package. func (p *servicePackage) UpdateTags(ctx context.Context, meta any, identifier string, oldTags, newTags any) error { - return updateTags(ctx, meta.(*conns.AWSClient).ConfigServiceConn(ctx), identifier, oldTags, newTags) + return updateTags(ctx, meta.(*conns.AWSClient).ConfigServiceClient(ctx), identifier, oldTags, newTags) } diff --git a/internal/service/configservice/validate.go b/internal/service/configservice/validate.go deleted file mode 100644 index f677244bbf8..00000000000 --- a/internal/service/configservice/validate.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configservice - -import ( - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" -) - -func validExecutionFrequency() schema.SchemaValidateFunc { - return validation.StringInSlice(configservice.MaximumExecutionFrequency_Values(), false) -} diff --git a/internal/service/configservice/wait.go b/internal/service/configservice/wait.go deleted file mode 100644 index e4dfabac5a1..00000000000 --- a/internal/service/configservice/wait.go +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package configservice - -import ( - "context" - "time" - - "github.com/aws/aws-sdk-go/service/configservice" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" -) - -const ( - ruleDeletedTimeout = 5 * time.Minute -) - -func waitRuleDeleted(ctx context.Context, conn *configservice.ConfigService, name string) (*configservice.ConfigRule, error) { - stateConf := &retry.StateChangeConf{ - Pending: []string{ - configservice.ConfigRuleStateActive, - configservice.ConfigRuleStateDeleting, - configservice.ConfigRuleStateDeletingResults, - configservice.ConfigRuleStateEvaluating, - }, - Target: []string{}, - Refresh: statusRule(ctx, conn, name), - Timeout: ruleDeletedTimeout, - } - - outputRaw, err := stateConf.WaitForStateContext(ctx) - - if v, ok := outputRaw.(*configservice.ConfigRule); ok { - return v, err - } - - return nil, err -} diff --git a/internal/service/lambda/invocation_test.go b/internal/service/lambda/invocation_test.go index 10a4a33087e..884e6353377 100644 --- a/internal/service/lambda/invocation_test.go +++ b/internal/service/lambda/invocation_test.go @@ -15,6 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-provider-aws/internal/acctest" + "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/verify" "github.com/hashicorp/terraform-provider-aws/names" ) @@ -277,7 +278,7 @@ func TestAccLambdaInvocation_lifecycle_scopeCRUDDestroy(t *testing.T) { testAccInvocationConfig_crudAllowSSM(rName, ssmParameterName), ), Check: resource.ComposeTestCheckFunc( - testAccCheckCRUDDestroyResult(ctx, t, resourceName, ssmParameterName, destroyJSON), + testAccCheckCRUDDestroyResult(ctx, resourceName, ssmParameterName, destroyJSON), ), }, }, @@ -365,13 +366,13 @@ func TestAccLambdaInvocation_terraformKey(t *testing.T) { // Because a destroy implies the resource will be removed from the state we need another way to check // how the lambda was invoked. The JSON used to invoke the lambda is stored in an SSM Parameter. // We will read it out, compare with the expected result and clean up the SSM parameter. -func testAccCheckCRUDDestroyResult(ctx context.Context, t *testing.T, name, ssmParameterName, expectedResult string) resource.TestCheckFunc { +func testAccCheckCRUDDestroyResult(ctx context.Context, name, ssmParameterName, expectedResult string) resource.TestCheckFunc { return func(s *terraform.State) error { _, ok := s.RootModule().Resources[name] if ok { return fmt.Errorf("Still found resource in state: %s", name) } - conn := acctest.ProviderMeta(t).SSMConn(ctx) + conn := acctest.Provider.Meta().(*conns.AWSClient).SSMConn(ctx) res, err := conn.GetParameterWithContext(ctx, &ssm.GetParameterInput{ Name: aws.String(ssmParameterName), WithDecryption: aws.Bool(true), diff --git a/internal/service/logs/group_test.go b/internal/service/logs/group_test.go index e3e6b61096a..5498499b2e7 100644 --- a/internal/service/logs/group_test.go +++ b/internal/service/logs/group_test.go @@ -29,7 +29,7 @@ func TestAccLogsGroup_basic(t *testing.T) { expectedLogGroupClass = "" } - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -63,7 +63,7 @@ func TestAccLogsGroup_nameGenerate(t *testing.T) { var v types.LogGroup resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -91,7 +91,7 @@ func TestAccLogsGroup_namePrefix(t *testing.T) { var v types.LogGroup resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -120,7 +120,7 @@ func TestAccLogsGroup_disappears(t *testing.T) { rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -144,7 +144,7 @@ func TestAccLogsGroup_tags(t *testing.T) { rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -192,7 +192,7 @@ func TestAccLogsGroup_kmsKey(t *testing.T) { kmsKey1ResourceName := "aws_kms_key.test.0" kmsKey2ResourceName := "aws_kms_key.test.1" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -234,7 +234,7 @@ func TestAccLogsGroup_logGroupClass(t *testing.T) { rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) // CloudWatch Logs IA is available in all AWS Commercial regions. @@ -261,7 +261,7 @@ func TestAccLogsGroup_retentionPolicy(t *testing.T) { rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -305,7 +305,7 @@ func TestAccLogsGroup_multiple(t *testing.T) { resource2Name := "aws_cloudwatch_log_group.test.1" resource3Name := "aws_cloudwatch_log_group.test.2" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -329,7 +329,7 @@ func TestAccLogsGroup_skipDestroy(t *testing.T) { rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -352,7 +352,7 @@ func TestAccLogsGroup_skipDestroyInconsistentPlan(t *testing.T) { rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_cloudwatch_log_group.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) }, ErrorCheck: acctest.ErrorCheck(t, names.LogsServiceID), ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, @@ -383,7 +383,7 @@ func testAccCheckGroupExists(ctx context.Context, t *testing.T, n string, v *typ return fmt.Errorf("Not found: %s", n) } - conn := acctest.ProviderMeta(t).LogsClient(ctx) + conn := acctest.ProviderMeta(ctx, t).LogsClient(ctx) output, err := tflogs.FindLogGroupByName(ctx, conn, rs.Primary.ID) @@ -399,7 +399,7 @@ func testAccCheckGroupExists(ctx context.Context, t *testing.T, n string, v *typ func testAccCheckGroupDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.ProviderMeta(t).LogsClient(ctx) + conn := acctest.ProviderMeta(ctx, t).LogsClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_cloudwatch_log_group" { @@ -425,7 +425,7 @@ func testAccCheckGroupDestroy(ctx context.Context, t *testing.T) resource.TestCh func testAccCheckGroupNoDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.ProviderMeta(t).LogsClient(ctx) + conn := acctest.ProviderMeta(ctx, t).LogsClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_cloudwatch_log_group" { diff --git a/internal/service/organizations/organization.go b/internal/service/organizations/organization.go index 65710a699cc..02abe220a05 100644 --- a/internal/service/organizations/organization.go +++ b/internal/service/organizations/organization.go @@ -388,6 +388,10 @@ func resourceOrganizationDelete(ctx context.Context, d *schema.ResourceData, met log.Printf("[INFO] Deleting Organization: %s", d.Id()) _, err := conn.DeleteOrganizationWithContext(ctx, &organizations.DeleteOrganizationInput{}) + if tfawserr.ErrCodeEquals(err, organizations.ErrCodeAWSOrganizationsNotInUseException) { + return diags + } + if err != nil { return sdkdiag.AppendErrorf(diags, "deleting Organization (%s): %s", d.Id(), err) } diff --git a/internal/service/scheduler/schedule_test.go b/internal/service/scheduler/schedule_test.go index a9c31d74ae8..25778bb8d79 100644 --- a/internal/service/scheduler/schedule_test.go +++ b/internal/service/scheduler/schedule_test.go @@ -188,7 +188,7 @@ func TestAccSchedulerSchedule_basic(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -247,7 +247,7 @@ func TestAccSchedulerSchedule_disappears(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -279,7 +279,7 @@ func TestAccSchedulerSchedule_description(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -339,7 +339,7 @@ func TestAccSchedulerSchedule_endDate(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -399,7 +399,7 @@ func TestAccSchedulerSchedule_flexibleTimeWindow(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -462,7 +462,7 @@ func TestAccSchedulerSchedule_groupName(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -498,7 +498,7 @@ func TestAccSchedulerSchedule_kmsKeyARN(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -557,7 +557,7 @@ func TestAccSchedulerSchedule_nameGenerated(t *testing.T) { var schedule scheduler.GetScheduleOutput resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -593,7 +593,7 @@ func TestAccSchedulerSchedule_namePrefix(t *testing.T) { var schedule scheduler.GetScheduleOutput resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -630,7 +630,7 @@ func TestAccSchedulerSchedule_scheduleExpression(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -678,7 +678,7 @@ func TestAccSchedulerSchedule_scheduleExpressionTimezone(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -738,7 +738,7 @@ func TestAccSchedulerSchedule_startDate(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -798,7 +798,7 @@ func TestAccSchedulerSchedule_state(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -858,7 +858,7 @@ func TestAccSchedulerSchedule_targetARN(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -906,7 +906,7 @@ func TestAccSchedulerSchedule_targetDeadLetterConfig(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -966,7 +966,7 @@ func TestAccSchedulerSchedule_targetECSParameters(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1142,7 +1142,7 @@ func TestAccSchedulerSchedule_targetEventBridgeParameters(t *testing.T) { eventBusName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1205,7 +1205,7 @@ func TestAccSchedulerSchedule_targetInput(t *testing.T) { resourceName := "aws_scheduler_schedule.test" var queueUrl string - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1270,7 +1270,7 @@ func TestAccSchedulerSchedule_targetKinesisParameters(t *testing.T) { streamName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1330,7 +1330,7 @@ func TestAccSchedulerSchedule_targetRetryPolicy(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1393,7 +1393,7 @@ func TestAccSchedulerSchedule_targetRoleARN(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1441,7 +1441,7 @@ func TestAccSchedulerSchedule_targetSageMakerPipelineParameters(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1537,7 +1537,7 @@ func TestAccSchedulerSchedule_targetSQSParameters(t *testing.T) { name := acctest.RandomWithPrefix(t, acctest.ResourcePrefix) resourceName := "aws_scheduler_schedule.test" - acctest.ParallelTest(t, resource.TestCase{ + acctest.ParallelTest(ctx, t, resource.TestCase{ PreCheck: func() { acctest.PreCheck(ctx, t) acctest.PreCheckPartitionHasService(t, names.SchedulerEndpointID) @@ -1589,7 +1589,7 @@ func TestAccSchedulerSchedule_targetSQSParameters(t *testing.T) { func testAccCheckScheduleDestroy(ctx context.Context, t *testing.T) resource.TestCheckFunc { return func(s *terraform.State) error { - conn := acctest.ProviderMeta(t).SchedulerClient(ctx) + conn := acctest.ProviderMeta(ctx, t).SchedulerClient(ctx) for _, rs := range s.RootModule().Resources { if rs.Type != "aws_scheduler_schedule" { @@ -1636,7 +1636,7 @@ func testAccCheckScheduleExists(ctx context.Context, t *testing.T, name string, return err } - conn := acctest.ProviderMeta(t).SchedulerClient(ctx) + conn := acctest.ProviderMeta(ctx, t).SchedulerClient(ctx) output, err := tfscheduler.FindScheduleByTwoPartKey(ctx, conn, groupName, scheduleName) diff --git a/internal/service/shield/service_package.go b/internal/service/shield/service_package.go index 9becec85da5..2db99253d07 100644 --- a/internal/service/shield/service_package.go +++ b/internal/service/shield/service_package.go @@ -6,23 +6,23 @@ package shield import ( "context" - aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" - shield_sdkv2 "github.com/aws/aws-sdk-go-v2/service/shield" - endpoints_sdkv1 "github.com/aws/aws-sdk-go/aws/endpoints" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/shield" + "github.com/hashicorp/terraform-provider-aws/names" ) // NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. -func (p *servicePackage) NewClient(_ context.Context, config map[string]any) (*shield_sdkv2.Client, error) { - cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) +func (p *servicePackage) NewClient(_ context.Context, config map[string]any) (*shield.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws.Config)) // Force "global" services to correct Regions. - if config["partition"].(string) == endpoints_sdkv1.AwsPartitionID { - cfg.Region = endpoints_sdkv1.UsEast1RegionID + if config["partition"].(string) == names.StandardPartitionID { + cfg.Region = names.USEast1RegionID } - return shield_sdkv2.NewFromConfig(cfg, func(o *shield_sdkv2.Options) { + return shield.NewFromConfig(cfg, func(o *shield.Options) { if endpoint := config["endpoint"].(string); endpoint != "" { - o.BaseEndpoint = aws_sdkv2.String(endpoint) + o.BaseEndpoint = aws.String(endpoint) } }), nil } diff --git a/internal/service/ssoadmin/service_package.go b/internal/service/ssoadmin/service_package.go index 1c20800c8fa..c9c3df9fba8 100644 --- a/internal/service/ssoadmin/service_package.go +++ b/internal/service/ssoadmin/service_package.go @@ -6,28 +6,28 @@ package ssoadmin import ( "context" - aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" - retry_sdkv2 "github.com/aws/aws-sdk-go-v2/aws/retry" - ssoadmin_sdkv2 "github.com/aws/aws-sdk-go-v2/service/ssoadmin" - ssoadmin_sdkv2_types "github.com/aws/aws-sdk-go-v2/service/ssoadmin/types" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/aws/retry" + "github.com/aws/aws-sdk-go-v2/service/ssoadmin" + "github.com/aws/aws-sdk-go-v2/service/ssoadmin/types" "github.com/hashicorp/terraform-provider-aws/internal/conns" "github.com/hashicorp/terraform-provider-aws/internal/errs" ) // NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. -func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*ssoadmin_sdkv2.Client, error) { - cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*ssoadmin.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws.Config)) - return ssoadmin_sdkv2.NewFromConfig(cfg, func(o *ssoadmin_sdkv2.Options) { + return ssoadmin.NewFromConfig(cfg, func(o *ssoadmin.Options) { if endpoint := config["endpoint"].(string); endpoint != "" { - o.BaseEndpoint = aws_sdkv2.String(endpoint) + o.BaseEndpoint = aws.String(endpoint) } - o.Retryer = conns.AddIsErrorRetryables(cfg.Retryer().(aws_sdkv2.RetryerV2), retry_sdkv2.IsErrorRetryableFunc(func(err error) aws_sdkv2.Ternary { - if errs.IsA[*ssoadmin_sdkv2_types.ConflictException](err) || - errs.IsA[*ssoadmin_sdkv2_types.ThrottlingException](err) { - return aws_sdkv2.TrueTernary + + o.Retryer = conns.AddIsErrorRetryables(cfg.Retryer().(aws.RetryerV2), retry.IsErrorRetryableFunc(func(err error) aws.Ternary { + if errs.IsA[*types.ConflictException](err) || errs.IsA[*types.ThrottlingException](err) { + return aws.TrueTernary } - return aws_sdkv2.UnknownTernary // Delegate to configured Retryer. + return aws.UnknownTernary // Delegate to configured Retryer. })) }), nil } diff --git a/internal/service/sts/service_package.go b/internal/service/sts/service_package.go index 6f8c1866a89..13dea08c226 100644 --- a/internal/service/sts/service_package.go +++ b/internal/service/sts/service_package.go @@ -6,32 +6,17 @@ package sts import ( "context" - aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" - sts_sdkv2 "github.com/aws/aws-sdk-go-v2/service/sts" - aws_sdkv1 "github.com/aws/aws-sdk-go/aws" - session_sdkv1 "github.com/aws/aws-sdk-go/aws/session" - sts_sdkv1 "github.com/aws/aws-sdk-go/service/sts" + "github.com/aws/aws-sdk-go-v2/aws" + "github.com/aws/aws-sdk-go-v2/service/sts" ) -// NewConn returns a new AWS SDK for Go v1 client for this service package's AWS API. -func (p *servicePackage) NewConn(ctx context.Context, config map[string]any) (*sts_sdkv1.STS, error) { - sess := config["session"].(*session_sdkv1.Session) - cfg := &aws_sdkv1.Config{Endpoint: aws_sdkv1.String(config["endpoint"].(string))} - - if stsRegion := config["sts_region"].(string); stsRegion != "" { - cfg.Region = aws_sdkv1.String(stsRegion) - } - - return sts_sdkv1.New(sess.Copy(cfg)), nil -} - // NewClient returns a new AWS SDK for Go v2 client for this service package's AWS API. -func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*sts_sdkv2.Client, error) { - cfg := *(config["aws_sdkv2_config"].(*aws_sdkv2.Config)) +func (p *servicePackage) NewClient(ctx context.Context, config map[string]any) (*sts.Client, error) { + cfg := *(config["aws_sdkv2_config"].(*aws.Config)) - return sts_sdkv2.NewFromConfig(cfg, func(o *sts_sdkv2.Options) { + return sts.NewFromConfig(cfg, func(o *sts.Options) { if endpoint := config["endpoint"].(string); endpoint != "" { - o.BaseEndpoint = aws_sdkv2.String(endpoint) + o.BaseEndpoint = aws.String(endpoint) } if stsRegion := config["sts_region"].(string); stsRegion != "" { diff --git a/internal/sweep/sdk/resource.go b/internal/sweep/sdk/resource.go index eb0c1a858c4..1cd5eb29f78 100644 --- a/internal/sweep/sdk/resource.go +++ b/internal/sweep/sdk/resource.go @@ -9,7 +9,7 @@ import ( "strings" "time" - awsretry "github.com/aws/aws-sdk-go-v2/aws/retry" + retry_sdkv2 "github.com/aws/aws-sdk-go-v2/aws/retry" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" @@ -42,6 +42,11 @@ func newSweepResource(resource *schema.Resource, d *schema.ResourceData, meta *c func (sr *sweepResource) Delete(ctx context.Context, timeout time.Duration, optFns ...tfresource.OptionsFunc) error { ctx = tflog.SetField(ctx, "id", sr.d.Id()) + // TODO + // TODO Once all services have moved to AWS SDK for Go v2 I _think_ we can remove this + // TODO custom retry logic as the API clients have been configured to use Adaptive retry. + // TODO + jitter := time.Duration(rand.Int63n(int64(1*time.Second))) - 1*time.Second/2 defaultOpts := []tfresource.OptionsFunc{ tfresource.WithMinPollInterval(2*time.Second + jitter), @@ -56,7 +61,7 @@ func (sr *sweepResource) Delete(ctx context.Context, timeout time.Duration, optF var throttled bool // The throttling error codes defined by the AWS SDK for Go v2 are a superset of the // codes defined by v1, so use the v2 codes here. - for _, code := range maps.Keys(awsretry.DefaultThrottleErrorCodes) { + for _, code := range maps.Keys(retry_sdkv2.DefaultThrottleErrorCodes) { // The resource delete operation returns a diag.Diagnostics, so we have to do a // string comparison instead of checking the error code of an actual error if strings.Contains(err.Error(), code) { diff --git a/internal/sweep/sweep.go b/internal/sweep/sweep.go index 4f23ff5c318..824b2bef957 100644 --- a/internal/sweep/sweep.go +++ b/internal/sweep/sweep.go @@ -10,7 +10,7 @@ import ( "strconv" "time" - "github.com/aws/aws-sdk-go/aws/endpoints" + aws_sdkv2 "github.com/aws/aws-sdk-go-v2/aws" multierror "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-provider-aws/internal/envvar" "github.com/hashicorp/terraform-provider-aws/internal/sweep/awsv1" "github.com/hashicorp/terraform-provider-aws/internal/tfresource" + "github.com/hashicorp/terraform-provider-aws/names" ) const ( @@ -63,6 +64,7 @@ func SharedRegionalSweepClient(ctx context.Context, region string) (*conns.AWSCl conf := &conns.Config{ Region: region, + RetryMode: aws_sdkv2.RetryModeAdaptive, SuppressDebugLog: true, } @@ -127,17 +129,11 @@ func SweepOrchestrator(ctx context.Context, sweepables []Sweepable, optFns ...tf var SkipSweepError = awsv1.SkipSweepError func Partition(region string) string { - if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), region); ok { - return partition.ID() - } - return "aws" + return names.PartitionForRegion(region) } func PartitionDNSSuffix(region string) string { - if partition, ok := endpoints.PartitionForRegion(endpoints.DefaultPartitions(), region); ok { - return partition.DNSSuffix() - } - return "amazonaws.com" + return names.DNSSuffixForPartition(Partition(region)) } type SweeperFn func(ctx context.Context, client *conns.AWSClient) ([]Sweepable, error) diff --git a/names/data/names_data.csv b/names/data/names_data.csv index 1ee85521f89..5cdab0b6574 100644 --- a/names/data/names_data.csv +++ b/names/data/names_data.csv @@ -64,7 +64,7 @@ cloudformation,cloudformation,cloudformation,cloudformation,,cloudformation,,,Cl cloudfront,cloudfront,cloudfront,cloudfront,,cloudfront,,,CloudFront,CloudFront,,1,2,,aws_cloudfront_,,cloudfront_,CloudFront,Amazon,,,,,,,CloudFront,ListDistributions,, cloudfront-keyvaluestore,cloudfrontkeyvaluestore,,cloudfrontkeyvaluestore,,cloudfrontkeyvaluestore,,,CloudFrontKeyValueStore,CloudFrontKeyValueStore,,,2,,aws_cloudfrontkeyvaluestore_,,cloudfront_keyvaluestore_,CloudFront KeyValueStore,Amazon,,,,,,,CloudFront KeyValueStore,ListKeys,"KvsARN: aws_sdkv2.String(""arn:aws:cloudfront::111122223333:key-value-store/MaxAge"")", cloudhsm,cloudhsm,cloudhsm,cloudhsm,,,,,,,,,,,,,,CloudHSM,AWS,x,,,,,,,,,Legacy -cloudhsmv2,cloudhsmv2,cloudhsmv2,cloudhsmv2,,cloudhsmv2,,cloudhsm,CloudHSMV2,CloudHSMV2,,,2,aws_cloudhsm_v2_,aws_cloudhsmv2_,,cloudhsm,CloudHSM,AWS,,,,,,,CloudHSM V2,DescribeClusters,, +cloudhsmv2,cloudhsmv2,cloudhsmv2,cloudhsmv2,,cloudhsmv2,,cloudhsm,CloudHSMV2,CloudHSMV2,x,,2,aws_cloudhsm_v2_,aws_cloudhsmv2_,,cloudhsm,CloudHSM,AWS,,,,,,,CloudHSM V2,DescribeClusters,, cloudsearch,cloudsearch,cloudsearch,cloudsearch,,cloudsearch,,,CloudSearch,CloudSearch,,,2,,aws_cloudsearch_,,cloudsearch_,CloudSearch,Amazon,,,,,,,CloudSearch,ListDomainNames,, cloudsearchdomain,cloudsearchdomain,cloudsearchdomain,cloudsearchdomain,,cloudsearchdomain,,,CloudSearchDomain,CloudSearchDomain,,1,,,aws_cloudsearchdomain_,,cloudsearchdomain_,CloudSearch Domain,Amazon,,x,,,,,CloudSearch Domain,,, ,,,,,,,,,,,,,,,,,CloudShell,AWS,x,,,,,,,,,No SDK support @@ -92,7 +92,7 @@ cognito-sync,cognitosync,cognitosync,cognitosync,,cognitosync,,,CognitoSync,Cogn comprehend,comprehend,comprehend,comprehend,,comprehend,,,Comprehend,Comprehend,,,2,,aws_comprehend_,,comprehend_,Comprehend,Amazon,,,,,,,Comprehend,ListDocumentClassifiers,, comprehendmedical,comprehendmedical,comprehendmedical,comprehendmedical,,comprehendmedical,,,ComprehendMedical,ComprehendMedical,,1,,,aws_comprehendmedical_,,comprehendmedical_,Comprehend Medical,Amazon,,x,,,,,ComprehendMedical,,, compute-optimizer,computeoptimizer,computeoptimizer,computeoptimizer,,computeoptimizer,,,ComputeOptimizer,ComputeOptimizer,,,2,,aws_computeoptimizer_,,computeoptimizer_,Compute Optimizer,AWS,,,,,,,Compute Optimizer,GetEnrollmentStatus,, -configservice,configservice,configservice,configservice,,configservice,,config,ConfigService,ConfigService,,1,,aws_config_,aws_configservice_,,config_,Config,AWS,,,,,,,Config Service,ListStoredQueries,, +configservice,configservice,configservice,configservice,,configservice,,config,ConfigService,ConfigService,,,2,aws_config_,aws_configservice_,,config_,Config,AWS,,,,,,,Config Service,ListStoredQueries,, connect,connect,connect,connect,,connect,,,Connect,Connect,,1,,,aws_connect_,,connect_,Connect,Amazon,,,,,,,Connect,ListInstances,, connectcases,connectcases,connectcases,connectcases,,connectcases,,,ConnectCases,ConnectCases,,,2,,aws_connectcases_,,connectcases_,Connect Cases,Amazon,,,,,,,ConnectCases,ListDomains,, connect-contact-lens,connectcontactlens,connectcontactlens,connectcontactlens,,connectcontactlens,,,ConnectContactLens,ConnectContactLens,,1,,,aws_connectcontactlens_,,connectcontactlens_,Connect Contact Lens,Amazon,,x,,,,,Connect Contact Lens,,, diff --git a/names/names.go b/names/names.go index 7c1317f4946..7a15d0aada3 100644 --- a/names/names.go +++ b/names/names.go @@ -41,6 +41,7 @@ const ( CodeGuruReviewerEndpointID = "codeguru-reviewer" CodeStarConnectionsEndpointID = "codestar-connections" ComprehendEndpointID = "comprehend" + ConfigServiceEndpointID = "config" ECREndpointID = "api.ecr" EKSEndpointID = "eks" EMREndpointID = "elasticmapreduce" diff --git a/website/docs/r/config_retention_configuration.html.markdown b/website/docs/r/config_retention_configuration.html.markdown new file mode 100644 index 00000000000..cd516be348e --- /dev/null +++ b/website/docs/r/config_retention_configuration.html.markdown @@ -0,0 +1,49 @@ +--- +subcategory: "Config" +layout: "aws" +page_title: "AWS: aws_config_retention_configuration" +description: |- + Provides a resource to manage the AWS Config retention configuration. +--- + +# Resource: aws_config_retention_configuration + +Provides a resource to manage the AWS Config retention configuration. +The retention configuration defines the number of days that AWS Config stores historical information. + +## Example Usage + +```terraform +resource "aws_config_retention_configuration" "example" { + retention_period_in_days = 90 +} +``` + +## Argument Reference + +This resource supports the following arguments: + +* `retention_period_in_days` - (Required) The number of days AWS Config stores historical information. + +## Attribute Reference + +This resource exports the following attributes in addition to the arguments above: + +* `name` - The name of the retention configuration object. The object is always named **default**. + +## Import + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import the AWS Config retention configuration using the `name`. For example: + +```terraform +import { + to = aws_config_retention_configuration.example + id = "default" +} +``` + +Using `terraform import`, import the AWS Config retention configuration using the `name`. For example: + +```console +% terraform import aws_config_retention_configuration.example default +```