diff --git a/.changelog/23144.txt b/.changelog/23144.txt new file mode 100644 index 000000000000..0e2250f4c6a0 --- /dev/null +++ b/.changelog/23144.txt @@ -0,0 +1,3 @@ +```release-note:bug +resource/aws_s3_bucket_lifecycle_configuration: Fix extraneous diffs especially after import +``` \ No newline at end of file diff --git a/internal/service/s3/bucket_lifecycle_configuration.go b/internal/service/s3/bucket_lifecycle_configuration.go index 560fc3eba499..1e8a64a0c48f 100644 --- a/internal/service/s3/bucket_lifecycle_configuration.go +++ b/internal/service/s3/bucket_lifecycle_configuration.go @@ -4,15 +4,20 @@ import ( "context" "fmt" "log" + "reflect" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/service/s3" "github.com/hashicorp/aws-sdk-go-base/v2/awsv1shim/v2/tfawserr" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" "github.com/hashicorp/terraform-provider-aws/internal/conns" + "github.com/hashicorp/terraform-provider-aws/internal/experimental/nullable" 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" ) @@ -114,14 +119,12 @@ func ResourceBucketLifecycleConfiguration() *schema.Resource { }, }, "object_size_greater_than": { - Type: schema.TypeInt, + Type: nullable.TypeNullableInt, Optional: true, - Default: 0, // API returns 0 }, "object_size_less_than": { - Type: schema.TypeInt, + Type: nullable.TypeNullableInt, Optional: true, - Default: 0, // API returns 0 }, "prefix": { Type: schema.TypeString, @@ -249,7 +252,7 @@ func resourceBucketLifecycleConfigurationCreate(ctx context.Context, d *schema.R rules, err := ExpandLifecycleRules(d.Get("rule").([]interface{})) if err != nil { - return diag.FromErr(fmt.Errorf("error creating S3 Lifecycle Configuration for bucket (%s): %w", bucket, err)) + return diag.Errorf("error creating S3 Lifecycle Configuration for bucket (%s): %s", bucket, err) } input := &s3.PutBucketLifecycleConfigurationInput{ @@ -268,11 +271,15 @@ func resourceBucketLifecycleConfigurationCreate(ctx context.Context, d *schema.R }) if err != nil { - return diag.FromErr(fmt.Errorf("error creating S3 Lifecycle Configuration for bucket (%s): %w", bucket, err)) + return diag.Errorf("error creating S3 Lifecycle Configuration for bucket (%s): %s", bucket, err) } d.SetId(CreateResourceID(bucket, expectedBucketOwner)) + if err = waitForLifecycleConfigurationRulesStatus(ctx, conn, bucket, expectedBucketOwner, rules); err != nil { + return diag.Errorf("error waiting for S3 Lifecycle Configuration for bucket (%s) to reach expected rules status after update: %s", d.Id(), err) + } + return resourceBucketLifecycleConfigurationRead(ctx, d, meta) } @@ -292,10 +299,35 @@ func resourceBucketLifecycleConfigurationRead(ctx context.Context, d *schema.Res input.ExpectedBucketOwner = aws.String(expectedBucketOwner) } - output, err := verify.RetryOnAWSCode(ErrCodeNoSuchLifecycleConfiguration, func() (interface{}, error) { - return conn.GetBucketLifecycleConfigurationWithContext(ctx, input) + var lastOutput, output *s3.GetBucketLifecycleConfigurationOutput + + err = resource.RetryContext(ctx, lifecycleConfigurationRulesSteadyTimeout, func() *resource.RetryError { + var err error + + time.Sleep(lifecycleConfigurationExtraRetryDelay) + + output, err = conn.GetBucketLifecycleConfigurationWithContext(ctx, input) + + if d.IsNewResource() && tfawserr.ErrCodeEquals(err, ErrCodeNoSuchLifecycleConfiguration, s3.ErrCodeNoSuchBucket) { + return resource.RetryableError(err) + } + + if err != nil { + return resource.NonRetryableError(err) + } + + if lastOutput == nil || !reflect.DeepEqual(*lastOutput, *output) { + lastOutput = output + return resource.RetryableError(fmt.Errorf("bucket lifecycle configuration has not stablized; trying again")) + } + + return nil }) + if tfresource.TimedOut(err) { + output, err = conn.GetBucketLifecycleConfigurationWithContext(ctx, input) + } + if !d.IsNewResource() && tfawserr.ErrCodeEquals(err, ErrCodeNoSuchLifecycleConfiguration, s3.ErrCodeNoSuchBucket) { log.Printf("[WARN] S3 Bucket Lifecycle Configuration (%s) not found, removing from state", d.Id()) d.SetId("") @@ -303,19 +335,13 @@ func resourceBucketLifecycleConfigurationRead(ctx context.Context, d *schema.Res } if err != nil { - return diag.FromErr(fmt.Errorf("error getting S3 Bucket Lifecycle Configuration (%s): %w", d.Id(), err)) - } - - lifecycleConfig, ok := output.(*s3.GetBucketLifecycleConfigurationOutput) - - if !ok || lifecycleConfig == nil { - return diag.FromErr(fmt.Errorf("error reading S3 Bucket Lifecycle Configuration (%s): empty output", d.Id())) + return diag.Errorf("error getting S3 Bucket Lifecycle Configuration (%s): %s", d.Id(), err) } d.Set("bucket", bucket) d.Set("expected_bucket_owner", expectedBucketOwner) - if err := d.Set("rule", FlattenLifecycleRules(lifecycleConfig.Rules)); err != nil { - return diag.FromErr(fmt.Errorf("error setting rule: %w", err)) + if err := d.Set("rule", FlattenLifecycleRules(output.Rules)); err != nil { + return diag.Errorf("error setting rule: %s", err) } return nil @@ -331,7 +357,7 @@ func resourceBucketLifecycleConfigurationUpdate(ctx context.Context, d *schema.R rules, err := ExpandLifecycleRules(d.Get("rule").([]interface{})) if err != nil { - return diag.FromErr(fmt.Errorf("error updating S3 Bucket Lifecycle Configuration rule: %w", err)) + return diag.Errorf("error updating S3 Bucket Lifecycle Configuration rule: %s", err) } input := &s3.PutBucketLifecycleConfigurationInput{ @@ -350,11 +376,11 @@ func resourceBucketLifecycleConfigurationUpdate(ctx context.Context, d *schema.R }) if err != nil { - return diag.FromErr(fmt.Errorf("error updating S3 Bucket Lifecycle Configuration (%s): %w", d.Id(), err)) + return diag.Errorf("error updating S3 Bucket Lifecycle Configuration (%s): %s", d.Id(), err) } if err := waitForLifecycleConfigurationRulesStatus(ctx, conn, bucket, expectedBucketOwner, rules); err != nil { - return diag.FromErr(fmt.Errorf("error waiting for S3 Lifecycle Configuration for bucket (%s) to reach expected rules status after update: %w", d.Id(), err)) + return diag.Errorf("error waiting for S3 Lifecycle Configuration for bucket (%s) to reach expected rules status after update: %s", d.Id(), err) } return resourceBucketLifecycleConfigurationRead(ctx, d, meta) @@ -383,7 +409,7 @@ func resourceBucketLifecycleConfigurationDelete(ctx context.Context, d *schema.R } if err != nil { - return diag.FromErr(fmt.Errorf("error deleting S3 Bucket Lifecycle Configuration (%s): %w", d.Id(), err)) + return diag.Errorf("error deleting S3 Bucket Lifecycle Configuration (%s): %s", d.Id(), err) } return nil diff --git a/internal/service/s3/bucket_lifecycle_configuration_test.go b/internal/service/s3/bucket_lifecycle_configuration_test.go index a327eaeb9783..063b3db93f77 100644 --- a/internal/service/s3/bucket_lifecycle_configuration_test.go +++ b/internal/service/s3/bucket_lifecycle_configuration_test.go @@ -36,7 +36,7 @@ func TestAccS3BucketLifecycleConfiguration_basic(t *testing.T) { resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ "expiration.#": "1", "expiration.0.days": "365", - "filter.#": "1", + "filter.#": "0", "id": rName, "status": tfs3.LifecycleRuleStatusEnabled, }), @@ -73,7 +73,7 @@ func TestAccS3BucketLifecycleConfiguration_disappears(t *testing.T) { }) } -func TestAccS3BucketLifecycleConfiguration_FilterWithPrefix(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_filterWithPrefix(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" currTime := time.Now() @@ -87,7 +87,7 @@ func TestAccS3BucketLifecycleConfiguration_FilterWithPrefix(t *testing.T) { CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_Basic_UpdateConfig(rName, date, "logs/"), + Config: testAccBucketLifecycleConfiguration_Basic_updateConfig(rName, date, "logs/"), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -106,7 +106,7 @@ func TestAccS3BucketLifecycleConfiguration_FilterWithPrefix(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccBucketLifecycleConfiguration_Basic_UpdateConfig(rName, dateUpdated, "tmp/"), + Config: testAccBucketLifecycleConfiguration_Basic_updateConfig(rName, dateUpdated, "tmp/"), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -128,7 +128,7 @@ func TestAccS3BucketLifecycleConfiguration_FilterWithPrefix(t *testing.T) { }) } -func TestAccS3BucketLifecycleConfiguration_DisableRule(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_disableRule(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -139,13 +139,13 @@ func TestAccS3BucketLifecycleConfiguration_DisableRule(t *testing.T) { CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_Basic_StatusConfig(rName, tfs3.LifecycleRuleStatusEnabled), + Config: testAccBucketLifecycleConfiguration_Basic_statusConfig(rName, tfs3.LifecycleRuleStatusEnabled), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), ), }, { - Config: testAccBucketLifecycleConfiguration_Basic_StatusConfig(rName, tfs3.LifecycleRuleStatusDisabled), + Config: testAccBucketLifecycleConfiguration_Basic_statusConfig(rName, tfs3.LifecycleRuleStatusDisabled), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -159,7 +159,7 @@ func TestAccS3BucketLifecycleConfiguration_DisableRule(t *testing.T) { ImportStateVerify: true, }, { - Config: testAccBucketLifecycleConfiguration_Basic_StatusConfig(rName, tfs3.LifecycleRuleStatusEnabled), + Config: testAccBucketLifecycleConfiguration_Basic_statusConfig(rName, tfs3.LifecycleRuleStatusEnabled), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -171,7 +171,7 @@ func TestAccS3BucketLifecycleConfiguration_DisableRule(t *testing.T) { }) } -func TestAccS3BucketLifecycleConfiguration_MultipleRules(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_multipleRules(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" date := time.Now() @@ -184,7 +184,7 @@ func TestAccS3BucketLifecycleConfiguration_MultipleRules(t *testing.T) { CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_MultipleRulesConfig(rName, expirationDate), + Config: testAccBucketLifecycleConfiguration_multipleRulesConfig(rName, expirationDate), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckResourceAttr(resourceName, "rule.#", "2"), @@ -226,7 +226,7 @@ func TestAccS3BucketLifecycleConfiguration_MultipleRules(t *testing.T) { }) } -func TestAccS3BucketLifecycleConfiguration_NonCurrentVersionExpiration(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_nonCurrentVersionExpiration(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -237,7 +237,7 @@ func TestAccS3BucketLifecycleConfiguration_NonCurrentVersionExpiration(t *testin CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_NonCurrentVersionExpirationConfig(rName), + Config: testAccBucketLifecycleConfiguration_nonCurrentVersionExpirationConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -255,7 +255,7 @@ func TestAccS3BucketLifecycleConfiguration_NonCurrentVersionExpiration(t *testin }) } -func TestAccS3BucketLifecycleConfiguration_NonCurrentVersionTransition(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_nonCurrentVersionTransition(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -266,7 +266,7 @@ func TestAccS3BucketLifecycleConfiguration_NonCurrentVersionTransition(t *testin CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_NonCurrentVersionTransitionConfig(rName), + Config: testAccBucketLifecycleConfiguration_nonCurrentVersionTransitionConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -292,7 +292,7 @@ func TestAccS3BucketLifecycleConfiguration_NonCurrentVersionTransition(t *testin } // Ensure backwards compatible with now-deprecated "prefix" configuration -func TestAccS3BucketLifecycleConfiguration_Prefix(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_prefix(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -303,7 +303,7 @@ func TestAccS3BucketLifecycleConfiguration_Prefix(t *testing.T) { CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_Basic_PrefixConfig(rName, "path1/"), + Config: testAccBucketLifecycleConfiguration_Basic_prefixConfig(rName, "path1/"), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckResourceAttrPair(resourceName, "bucket", "aws_s3_bucket.test", "bucket"), @@ -326,7 +326,7 @@ func TestAccS3BucketLifecycleConfiguration_Prefix(t *testing.T) { }) } -func TestAccS3BucketLifecycleConfiguration_RuleExpiration_ExpireMarkerOnly(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_RuleExpiration_expireMarkerOnly(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -337,7 +337,7 @@ func TestAccS3BucketLifecycleConfiguration_RuleExpiration_ExpireMarkerOnly(t *te CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_RuleExpiration_ExpiredDeleteMarkerConfig(rName, true), + Config: testAccBucketLifecycleConfiguration_RuleExpiration_expiredDeleteMarkerConfig(rName, true), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -352,7 +352,7 @@ func TestAccS3BucketLifecycleConfiguration_RuleExpiration_ExpireMarkerOnly(t *te ImportStateVerify: true, }, { - Config: testAccBucketLifecycleConfiguration_RuleExpiration_ExpiredDeleteMarkerConfig(rName, false), + Config: testAccBucketLifecycleConfiguration_RuleExpiration_expiredDeleteMarkerConfig(rName, false), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -371,7 +371,7 @@ func TestAccS3BucketLifecycleConfiguration_RuleExpiration_ExpireMarkerOnly(t *te } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/11420 -func TestAccS3BucketLifecycleConfiguration_RuleExpiration_EmptyBlock(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_RuleExpiration_emptyBlock(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -382,7 +382,7 @@ func TestAccS3BucketLifecycleConfiguration_RuleExpiration_EmptyBlock(t *testing. CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_RuleExpiration_EmptyConfigurationBlockConfig(rName), + Config: testAccBucketLifecycleConfiguration_RuleExpiration_emptyConfigurationBlockConfig(rName), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -400,7 +400,7 @@ func TestAccS3BucketLifecycleConfiguration_RuleExpiration_EmptyBlock(t *testing. } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/15138 -func TestAccS3BucketLifecycleConfiguration_RuleAbortIncompleteMultipartUpload(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_ruleAbortIncompleteMultipartUpload(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -411,7 +411,7 @@ func TestAccS3BucketLifecycleConfiguration_RuleAbortIncompleteMultipartUpload(t CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_RuleAbortIncompleteMultipartUploadConfig(rName, 7), + Config: testAccBucketLifecycleConfiguration_ruleAbortIncompleteMultipartUploadConfig(rName, 7), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -426,7 +426,7 @@ func TestAccS3BucketLifecycleConfiguration_RuleAbortIncompleteMultipartUpload(t ImportStateVerify: true, }, { - Config: testAccBucketLifecycleConfiguration_RuleAbortIncompleteMultipartUploadConfig(rName, 5), + Config: testAccBucketLifecycleConfiguration_ruleAbortIncompleteMultipartUploadConfig(rName, 5), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.*", map[string]string{ @@ -444,10 +444,10 @@ func TestAccS3BucketLifecycleConfiguration_RuleAbortIncompleteMultipartUpload(t }) } -// TestAccS3BucketLifecycleConfiguration_TransitionDate_StandardIa validates the change to address +// TestAccS3BucketLifecycleConfiguration_TransitionDate_standardIa validates the change to address // https://github.com/hashicorp/terraform-provider-aws/issues/23117 // does not introduce a regression. -func TestAccS3BucketLifecycleConfiguration_TransitionDate_StandardIa(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_TransitionDate_standardIa(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -461,7 +461,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionDate_StandardIa(t *testing. CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_DateTransitionConfig(rName, date, s3.TransitionStorageClassStandardIa), + Config: testAccBucketLifecycleConfiguration_dateTransitionConfig(rName, date, s3.TransitionStorageClassStandardIa), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), ), @@ -475,10 +475,10 @@ func TestAccS3BucketLifecycleConfiguration_TransitionDate_StandardIa(t *testing. }) } -// TestAccS3BucketLifecycleConfiguration_TransitionDate_IntelligentTiering validates the change to address +// TestAccS3BucketLifecycleConfiguration_TransitionDate_intelligentTiering validates the change to address // https://github.com/hashicorp/terraform-provider-aws/issues/23117 // does not introduce a regression. -func TestAccS3BucketLifecycleConfiguration_TransitionDate_IntelligentTiering(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_TransitionDate_intelligentTiering(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -492,7 +492,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionDate_IntelligentTiering(t * CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_DateTransitionConfig(rName, date, s3.StorageClassIntelligentTiering), + Config: testAccBucketLifecycleConfiguration_dateTransitionConfig(rName, date, s3.StorageClassIntelligentTiering), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), ), @@ -507,7 +507,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionDate_IntelligentTiering(t * } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/23117 -func TestAccS3BucketLifecycleConfiguration_TransitionStorageClassOnly_IntelligentTiering(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_TransitionStorageClassOnly_intelligentTiering(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -518,7 +518,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionStorageClassOnly_Intelligen CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_TransitionStorageClassOnlyConfig(rName, s3.StorageClassIntelligentTiering), + Config: testAccBucketLifecycleConfiguration_transitionStorageClassOnlyConfig(rName, s3.StorageClassIntelligentTiering), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), resource.TestCheckTypeSetElemNestedAttrs(resourceName, "rule.0.transition.*", map[string]string{ @@ -538,7 +538,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionStorageClassOnly_Intelligen } // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/23117 -func TestAccS3BucketLifecycleConfiguration_TransitionZeroDays_IntelligentTiering(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_TransitionZeroDays_intelligentTiering(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -549,7 +549,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionZeroDays_IntelligentTiering CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_ZeroDaysTransitionConfig(rName, s3.StorageClassIntelligentTiering), + Config: testAccBucketLifecycleConfiguration_zeroDaysTransitionConfig(rName, s3.StorageClassIntelligentTiering), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), ), @@ -563,7 +563,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionZeroDays_IntelligentTiering }) } -func TestAccS3BucketLifecycleConfiguration_TransitionUpdateBetweenDaysAndDate_IntelligentTiering(t *testing.T) { +func TestAccS3BucketLifecycleConfiguration_TransitionUpdateBetweenDaysAndDate_intelligentTiering(t *testing.T) { rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix) resourceName := "aws_s3_bucket_lifecycle_configuration.test" @@ -577,13 +577,13 @@ func TestAccS3BucketLifecycleConfiguration_TransitionUpdateBetweenDaysAndDate_In CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy, Steps: []resource.TestStep{ { - Config: testAccBucketLifecycleConfiguration_ZeroDaysTransitionConfig(rName, s3.StorageClassIntelligentTiering), + Config: testAccBucketLifecycleConfiguration_zeroDaysTransitionConfig(rName, s3.StorageClassIntelligentTiering), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), ), }, { - Config: testAccBucketLifecycleConfiguration_DateTransitionConfig(rName, date, s3.StorageClassIntelligentTiering), + Config: testAccBucketLifecycleConfiguration_dateTransitionConfig(rName, date, s3.StorageClassIntelligentTiering), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), ), @@ -594,7 +594,7 @@ func TestAccS3BucketLifecycleConfiguration_TransitionUpdateBetweenDaysAndDate_In ImportStateVerify: true, }, { - Config: testAccBucketLifecycleConfiguration_ZeroDaysTransitionConfig(rName, s3.StorageClassIntelligentTiering), + Config: testAccBucketLifecycleConfiguration_zeroDaysTransitionConfig(rName, s3.StorageClassIntelligentTiering), Check: resource.ComposeTestCheckFunc( testAccCheckBucketLifecycleConfigurationExists(resourceName), ), @@ -706,15 +706,12 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { expiration { days = 365 } - - # One of prefix or filter required to ensure XML is well-formed - filter {} } } `, rName) } -func testAccBucketLifecycleConfiguration_Basic_StatusConfig(rName, status string) string { +func testAccBucketLifecycleConfiguration_Basic_statusConfig(rName, status string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -734,15 +731,12 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { expiration { days = 365 } - - # One of prefix or filter required to ensure XML is well-formed - filter {} } } `, rName, status) } -func testAccBucketLifecycleConfiguration_Basic_UpdateConfig(rName, date, prefix string) string { +func testAccBucketLifecycleConfiguration_Basic_updateConfig(rName, date, prefix string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -772,7 +766,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { `, rName, date, prefix) } -func testAccBucketLifecycleConfiguration_Basic_PrefixConfig(rName, prefix string) string { +func testAccBucketLifecycleConfiguration_Basic_prefixConfig(rName, prefix string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -800,7 +794,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { `, rName, prefix) } -func testAccBucketLifecycleConfiguration_RuleExpiration_ExpiredDeleteMarkerConfig(rName string, expired bool) string { +func testAccBucketLifecycleConfiguration_RuleExpiration_expiredDeleteMarkerConfig(rName string, expired bool) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -821,15 +815,12 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { expiration { expired_object_delete_marker = %[2]t } - - # One of prefix or filter required to ensure XML is well-formed - filter {} } } `, rName, expired) } -func testAccBucketLifecycleConfiguration_RuleExpiration_EmptyConfigurationBlockConfig(rName string) string { +func testAccBucketLifecycleConfiguration_RuleExpiration_emptyConfigurationBlockConfig(rName string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -848,15 +839,12 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { status = "Enabled" expiration {} - - # One of prefix or filter required to ensure XML is well-formed - filter {} } } `, rName) } -func testAccBucketLifecycleConfiguration_RuleAbortIncompleteMultipartUploadConfig(rName string, days int) string { +func testAccBucketLifecycleConfiguration_ruleAbortIncompleteMultipartUploadConfig(rName string, days int) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -877,15 +865,12 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { id = %[1]q status = "Enabled" - - # One of prefix or filter required to ensure XML is well-formed - filter {} } } `, rName, days) } -func testAccBucketLifecycleConfiguration_MultipleRulesConfig(rName, date string) string { +func testAccBucketLifecycleConfiguration_multipleRulesConfig(rName, date string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -947,7 +932,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { `, rName, date) } -func testAccBucketLifecycleConfiguration_NonCurrentVersionExpirationConfig(rName string) string { +func testAccBucketLifecycleConfiguration_nonCurrentVersionExpirationConfig(rName string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -979,7 +964,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { `, rName) } -func testAccBucketLifecycleConfiguration_NonCurrentVersionTransitionConfig(rName string) string { +func testAccBucketLifecycleConfiguration_nonCurrentVersionTransitionConfig(rName string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -1017,7 +1002,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { `, rName) } -func testAccBucketLifecycleConfiguration_TransitionStorageClassOnlyConfig(rName, storageClass string) string { +func testAccBucketLifecycleConfiguration_transitionStorageClassOnlyConfig(rName, storageClass string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -1053,7 +1038,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { `, rName, storageClass) } -func testAccBucketLifecycleConfiguration_ZeroDaysTransitionConfig(rName, storageClass string) string { +func testAccBucketLifecycleConfiguration_zeroDaysTransitionConfig(rName, storageClass string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q @@ -1090,7 +1075,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" { `, rName, storageClass) } -func testAccBucketLifecycleConfiguration_DateTransitionConfig(rName, transitionDate, storageClass string) string { +func testAccBucketLifecycleConfiguration_dateTransitionConfig(rName, transitionDate, storageClass string) string { return fmt.Sprintf(` resource "aws_s3_bucket" "test" { bucket = %[1]q diff --git a/internal/service/s3/flex.go b/internal/service/s3/flex.go index ca1c1dd82292..cc870abd82e2 100644 --- a/internal/service/s3/flex.go +++ b/internal/service/s3/flex.go @@ -883,7 +883,15 @@ func FlattenLifecycleRuleExpiration(expiration *s3.LifecycleExpiration) []interf func FlattenLifecycleRuleFilter(filter *s3.LifecycleRuleFilter) []interface{} { if filter == nil { - return []interface{}{} + return nil + } + + if filter.And == nil && + filter.ObjectSizeGreaterThan == nil && + filter.ObjectSizeLessThan == nil && + (filter.Prefix == nil || aws.StringValue(filter.Prefix) == "") && + filter.Tag == nil { + return nil } m := make(map[string]interface{}) @@ -900,7 +908,7 @@ func FlattenLifecycleRuleFilter(filter *s3.LifecycleRuleFilter) []interface{} { m["object_size_less_than"] = int(aws.Int64Value(filter.ObjectSizeLessThan)) } - if filter.Prefix != nil { + if filter.Prefix != nil && aws.StringValue(filter.Prefix) != "" { m["prefix"] = aws.StringValue(filter.Prefix) } @@ -939,7 +947,7 @@ func FlattenLifecycleRuleFilterAndOperator(andOp *s3.LifecycleRuleAndOperator) [ func FlattenLifecycleRuleFilterTag(tag *s3.Tag) []interface{} { if tag == nil { - return []interface{}{} + return nil } t := KeyValueTags([]*s3.Tag{tag}).IgnoreAWS().Map() diff --git a/internal/service/s3/status.go b/internal/service/s3/status.go index ecea4601ffa1..734618469679 100644 --- a/internal/service/s3/status.go +++ b/internal/service/s3/status.go @@ -21,7 +21,7 @@ func lifecycleConfigurationRulesStatus(ctx context.Context, conn *s3.S3, bucket, output, err := conn.GetBucketLifecycleConfigurationWithContext(ctx, input) - if tfawserr.ErrCodeEquals(err, ErrCodeNoSuchLifecycleConfiguration) { + if tfawserr.ErrCodeEquals(err, ErrCodeNoSuchLifecycleConfiguration, s3.ErrCodeNoSuchBucket) { return nil, "", nil } diff --git a/internal/service/s3/wait.go b/internal/service/s3/wait.go index 569326037e3a..77e9e20e8ee7 100644 --- a/internal/service/s3/wait.go +++ b/internal/service/s3/wait.go @@ -11,9 +11,11 @@ import ( const ( bucketCreatedTimeout = 2 * time.Minute - propagationTimeout = 1 * time.Minute - lifecycleConfigurationRulesPropagationTimeout = 2 * time.Minute bucketVersioningStableTimeout = 1 * time.Minute + lifecycleConfigurationExtraRetryDelay = 5 * time.Second + lifecycleConfigurationRulesPropagationTimeout = 3 * time.Minute + lifecycleConfigurationRulesSteadyTimeout = 2 * time.Minute + propagationTimeout = 1 * time.Minute // LifecycleConfigurationRulesStatusReady occurs when all configured rules reach their desired state (Enabled or Disabled) LifecycleConfigurationRulesStatusReady = "READY" @@ -27,10 +29,13 @@ func retryWhenBucketNotFound(f func() (interface{}, error)) (interface{}, error) func waitForLifecycleConfigurationRulesStatus(ctx context.Context, conn *s3.S3, bucket, expectedBucketOwner string, rules []*s3.LifecycleRule) error { stateConf := &resource.StateChangeConf{ - Pending: []string{"", LifecycleConfigurationRulesStatusNotReady}, - Target: []string{LifecycleConfigurationRulesStatusReady}, - Refresh: lifecycleConfigurationRulesStatus(ctx, conn, bucket, expectedBucketOwner, rules), - Timeout: lifecycleConfigurationRulesPropagationTimeout, + Pending: []string{"", LifecycleConfigurationRulesStatusNotReady}, + Target: []string{LifecycleConfigurationRulesStatusReady}, + Refresh: lifecycleConfigurationRulesStatus(ctx, conn, bucket, expectedBucketOwner, rules), + Timeout: lifecycleConfigurationRulesPropagationTimeout, + MinTimeout: 10 * time.Second, + ContinuousTargetOccurence: 3, + NotFoundChecks: 20, } _, err := stateConf.WaitForState() diff --git a/website/docs/r/s3_bucket_lifecycle_configuration.html.markdown b/website/docs/r/s3_bucket_lifecycle_configuration.html.markdown index 557410cb8c05..5e3ef2e4ccf9 100644 --- a/website/docs/r/s3_bucket_lifecycle_configuration.html.markdown +++ b/website/docs/r/s3_bucket_lifecycle_configuration.html.markdown @@ -151,11 +151,11 @@ The `rule` configuration block supports the following arguments: * `abort_incomplete_multipart_upload` - (Optional) Configuration block that specifies the days since the initiation of an incomplete multipart upload that Amazon S3 will wait before permanently removing all parts of the upload [documented below](#abort_incomplete_multipart_upload). * `expiration` - (Optional) Configuration block that specifies the expiration for the lifecycle of the object in the form of date, days and, whether the object has a delete marker [documented below](#expiration). -* `filter` - (Optional, Required if `prefix` not specified) Configuration block used to identify objects that a Lifecycle Rule applies to [documented below](#filter). +* `filter` - (Optional) Configuration block used to identify objects that a Lifecycle Rule applies to [documented below](#filter). * `id` - (Required) Unique identifier for the rule. The value cannot be longer than 255 characters. * `noncurrent_version_expiration` - (Optional) Configuration block that specifies when noncurrent object versions expire [documented below](#noncurrent_version_expiration). * `noncurrent_version_transition` - (Optional) Set of configuration blocks that specify the transition rule for the lifecycle rule that describes when noncurrent objects transition to a specific storage class [documented below](#noncurrent_version_transition). -* `prefix` - (Optional, Required if `filter` not specified) Prefix identifying one or more objects to which the rule applies. This has been deprecated by Amazon S3 and `filter` should be used instead. +* `prefix` - (Optional) **DEPRECATED** Use `filter` instead. This has been deprecated by Amazon S3. Prefix identifying one or more objects to which the rule applies. * `status` - (Required) Whether the rule is currently being applied. Valid values: `Enabled` or `Disabled`. * `transition` - (Optional) Set of configuration blocks that specify when an Amazon S3 object transitions to a specified storage class [documented below](#transition).