@@ -1229,7 +1229,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
12291229 //
12301230 // Bucket Policy.
12311231 //
1232- if d .HasChange (names .AttrPolicy ) {
1232+ // Only act if the practitioner has explicitly configured `policy` in HCL.
1233+ // When unset, defer to the dedicated `aws_s3_bucket_policy` resource.
1234+ if d .HasChange (names .AttrPolicy ) && deprecatedAttributeInRawConfig (d , names .AttrPolicy ) {
12331235 policy , err := structure .NormalizeJsonString (d .Get (names .AttrPolicy ).(string ))
12341236 if err != nil {
12351237 return sdkdiag .AppendFromErr (diags , err )
@@ -1264,7 +1266,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
12641266 //
12651267 // Bucket CORS Configuration.
12661268 //
1267- if d .HasChange ("cors_rule" ) {
1269+ // Only act if the practitioner has explicitly configured `cors_rule` in HCL.
1270+ // When unset, defer to the dedicated `aws_s3_bucket_cors_configuration` resource.
1271+ if d .HasChange ("cors_rule" ) && deprecatedAttributeInRawConfig (d , "cors_rule" ) {
12681272 if v , ok := d .GetOk ("cors_rule" ); ! ok || len (v .([]any )) == 0 {
12691273 _ , err := tfresource .RetryWhenAWSErrCodeEquals (ctx , d .Timeout (schema .TimeoutUpdate ), func (ctx context.Context ) (any , error ) {
12701274 return conn .DeleteBucketCors (ctx , & s3.DeleteBucketCorsInput {
@@ -1296,7 +1300,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
12961300 //
12971301 // Bucket Website Configuration.
12981302 //
1299- if d .HasChange ("website" ) {
1303+ // Only act if the practitioner has explicitly configured `website` in HCL.
1304+ // When unset, defer to the dedicated `aws_s3_bucket_website_configuration` resource.
1305+ if d .HasChange ("website" ) && deprecatedAttributeInRawConfig (d , "website" ) {
13001306 if v , ok := d .GetOk ("website" ); ! ok || len (v .([]any )) == 0 || v .([]any )[0 ] == nil {
13011307 _ , err := tfresource .RetryWhenAWSErrCodeEquals (ctx , d .Timeout (schema .TimeoutUpdate ), func (ctx context.Context ) (any , error ) {
13021308 return conn .DeleteBucketWebsite (ctx , & s3.DeleteBucketWebsiteInput {
@@ -1331,7 +1337,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
13311337 //
13321338 // Bucket Versioning.
13331339 //
1334- if d .HasChange ("versioning" ) {
1340+ // Only act if the practitioner has explicitly configured `versioning` in HCL.
1341+ // When unset, defer to the dedicated `aws_s3_bucket_versioning` resource.
1342+ if d .HasChange ("versioning" ) && deprecatedAttributeInRawConfig (d , "versioning" ) {
13351343 v := d .Get ("versioning" ).([]any )
13361344 var versioningConfig * types.VersioningConfiguration
13371345
@@ -1360,7 +1368,11 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
13601368 //
13611369 // Bucket ACL.
13621370 //
1363- if (d .HasChange ("acl" ) && ! d .IsNewResource ()) || (d .HasChange ("grant" ) && d .Get ("grant" ).(* schema.Set ).Len () == 0 ) {
1371+ // Only act if the practitioner has explicitly configured `acl` or `grant` in HCL.
1372+ // When unset, defer to the dedicated `aws_s3_bucket_acl` resource.
1373+ aclChanged := d .HasChange ("acl" ) && ! d .IsNewResource () && deprecatedAttributeInRawConfig (d , "acl" )
1374+ grantEmptied := d .HasChange ("grant" ) && d .Get ("grant" ).(* schema.Set ).Len () == 0 && deprecatedAttributeInRawConfig (d , "grant" )
1375+ if aclChanged || grantEmptied {
13641376 acl := types .BucketCannedACL (d .Get ("acl" ).(string ))
13651377 if acl == "" {
13661378 // Use default value previously available in v3.x of the provider.
@@ -1380,7 +1392,7 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
13801392 }
13811393 }
13821394
1383- if d .HasChange ("grant" ) && d .Get ("grant" ).(* schema.Set ).Len () > 0 {
1395+ if d .HasChange ("grant" ) && d .Get ("grant" ).(* schema.Set ).Len () > 0 && deprecatedAttributeInRawConfig ( d , "grant" ) {
13841396 bucketACL , err := retryWhenNoSuchBucketError (ctx , d .Timeout (schema .TimeoutUpdate ), func (ctx context.Context ) (* s3.GetBucketAclOutput , error ) {
13851397 return findBucketACL (ctx , conn , d .Id (), "" )
13861398 })
@@ -1409,7 +1421,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
14091421 //
14101422 // Bucket Logging.
14111423 //
1412- if d .HasChange ("logging" ) {
1424+ // Only act if the practitioner has explicitly configured `logging` in HCL.
1425+ // When unset, defer to the dedicated `aws_s3_bucket_logging` resource.
1426+ if d .HasChange ("logging" ) && deprecatedAttributeInRawConfig (d , "logging" ) {
14131427 input := & s3.PutBucketLoggingInput {
14141428 Bucket : aws .String (d .Id ()),
14151429 BucketLoggingStatus : & types.BucketLoggingStatus {},
@@ -1441,7 +1455,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
14411455 //
14421456 // Bucket Lifecycle Configuration.
14431457 //
1444- if d .HasChange ("lifecycle_rule" ) {
1458+ // Only act if the practitioner has explicitly configured `lifecycle_rule` in HCL.
1459+ // When unset, defer to the dedicated `aws_s3_bucket_lifecycle_configuration` resource.
1460+ if d .HasChange ("lifecycle_rule" ) && deprecatedAttributeInRawConfig (d , "lifecycle_rule" ) {
14451461 if v , ok := d .GetOk ("lifecycle_rule" ); ! ok || len (v .([]any )) == 0 {
14461462 _ , err := tfresource .RetryWhenAWSErrCodeEquals (ctx , d .Timeout (schema .TimeoutUpdate ), func (ctx context.Context ) (any , error ) {
14471463 return conn .DeleteBucketLifecycle (ctx , & s3.DeleteBucketLifecycleInput {
@@ -1473,7 +1489,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
14731489 //
14741490 // Bucket Accelerate Configuration.
14751491 //
1476- if d .HasChange ("acceleration_status" ) {
1492+ // Only act if the practitioner has explicitly configured `acceleration_status` in HCL.
1493+ // When unset, defer to the dedicated `aws_s3_bucket_accelerate_configuration` resource.
1494+ if d .HasChange ("acceleration_status" ) && deprecatedAttributeInRawConfig (d , "acceleration_status" ) {
14771495 input := & s3.PutBucketAccelerateConfigurationInput {
14781496 AccelerateConfiguration : & types.AccelerateConfiguration {
14791497 Status : types .BucketAccelerateStatus (d .Get ("acceleration_status" ).(string )),
@@ -1493,7 +1511,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
14931511 //
14941512 // Bucket Request Payment Configuration.
14951513 //
1496- if d .HasChange ("request_payer" ) {
1514+ // Only act if the practitioner has explicitly configured `request_payer` in HCL.
1515+ // When unset, defer to the dedicated `aws_s3_bucket_request_payment_configuration` resource.
1516+ if d .HasChange ("request_payer" ) && deprecatedAttributeInRawConfig (d , "request_payer" ) {
14971517 input := & s3.PutBucketRequestPaymentInput {
14981518 Bucket : aws .String (d .Id ()),
14991519 RequestPaymentConfiguration : & types.RequestPaymentConfiguration {
@@ -1513,7 +1533,13 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
15131533 //
15141534 // Bucket Replication Configuration.
15151535 //
1516- if d .HasChange ("replication_configuration" ) {
1536+ // Only act if the practitioner has explicitly configured `replication_configuration` in HCL.
1537+ // When unset, defer to the dedicated `aws_s3_bucket_replication_configuration` resource. This
1538+ // avoids round-tripping fields not represented in this deprecated schema (e.g.
1539+ // `replica_modifications`) and writing back a corrupted configuration on unrelated bucket
1540+ // updates such as tags.
1541+ // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/44192
1542+ if d .HasChange ("replication_configuration" ) && deprecatedAttributeInRawConfig (d , "replication_configuration" ) {
15171543 if v , ok := d .GetOk ("replication_configuration" ); ! ok || len (v .([]any )) == 0 || v .([]any )[0 ] == nil {
15181544 _ , err := tfresource .RetryWhenAWSErrCodeEquals (ctx , d .Timeout (schema .TimeoutUpdate ), func (ctx context.Context ) (any , error ) {
15191545 return conn .DeleteBucketReplication (ctx , & s3.DeleteBucketReplicationInput {
@@ -1567,7 +1593,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
15671593 //
15681594 // Bucket Server-side Encryption Configuration.
15691595 //
1570- if d .HasChange ("server_side_encryption_configuration" ) {
1596+ // Only act if the practitioner has explicitly configured `server_side_encryption_configuration` in HCL.
1597+ // When unset, defer to the dedicated `aws_s3_bucket_server_side_encryption_configuration` resource.
1598+ if d .HasChange ("server_side_encryption_configuration" ) && deprecatedAttributeInRawConfig (d , "server_side_encryption_configuration" ) {
15711599 if v , ok := d .GetOk ("server_side_encryption_configuration" ); ! ok || len (v .([]any )) == 0 {
15721600 _ , err := tfresource .RetryWhenAWSErrCodeEquals (ctx , d .Timeout (schema .TimeoutUpdate ), func (ctx context.Context ) (any , error ) {
15731601 return conn .DeleteBucketEncryption (ctx , & s3.DeleteBucketEncryptionInput {
@@ -1599,7 +1627,9 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
15991627 //
16001628 // Bucket Object Lock Configuration.
16011629 //
1602- if d .HasChange ("object_lock_configuration" ) {
1630+ // Only act if the practitioner has explicitly configured `object_lock_configuration` in HCL.
1631+ // When unset, defer to the dedicated `aws_s3_bucket_object_lock_configuration` resource.
1632+ if d .HasChange ("object_lock_configuration" ) && deprecatedAttributeInRawConfig (d , "object_lock_configuration" ) {
16031633 // S3 Object Lock configuration cannot be deleted, only updated.
16041634 input := & s3.PutObjectLockConfigurationInput {
16051635 Bucket : aws .String (d .Id ()),
@@ -1618,6 +1648,37 @@ func resourceBucketUpdate(ctx context.Context, d *schema.ResourceData, meta any)
16181648 return append (diags , resourceBucketRead (ctx , d , meta )... )
16191649}
16201650
1651+ // deprecatedAttributeInRawConfig reports whether the practitioner has the
1652+ // named top-level attribute (a deprecated bucket nested attribute) in their
1653+ // HCL configuration. When the value in state was populated solely by Read
1654+ // (i.e. the attribute is Computed-only at runtime), this returns false, and
1655+ // callers should skip the corresponding API update. This avoids the
1656+ // deprecated attribute fighting with its dedicated standalone resource over
1657+ // fields not represented in the deprecated schema, which can write back
1658+ // corrupted configuration on unrelated bucket updates such as tags.
1659+ //
1660+ // Reference: https://github.com/hashicorp/terraform-provider-aws/issues/44192
1661+ func deprecatedAttributeInRawConfig (d * schema.ResourceData , name string ) bool {
1662+ rawConfig := d .GetRawConfig ()
1663+ if ! rawConfig .IsKnown () || rawConfig .IsNull () {
1664+ return false
1665+ }
1666+
1667+ v := rawConfig .GetAttr (name )
1668+ if ! v .IsKnown () || v .IsNull () {
1669+ return false
1670+ }
1671+
1672+ // For collection types (most commonly nested block lists/sets), an
1673+ // unconfigured attribute reports as a non-null empty collection rather
1674+ // than null, so check the length explicitly.
1675+ if t := v .Type (); t .IsListType () || t .IsSetType () || t .IsMapType () || t .IsTupleType () || t .IsObjectType () {
1676+ return v .LengthInt () > 0
1677+ }
1678+
1679+ return true
1680+ }
1681+
16211682func resourceBucketDelete (ctx context.Context , d * schema.ResourceData , meta any ) diag.Diagnostics {
16221683 var diags diag.Diagnostics
16231684 conn := meta .(* conns.AWSClient ).S3Client (ctx )
0 commit comments