Skip to content

Commit 5e0ac8d

Browse files
authored
Merge pull request #23232 from hashicorp/b-s3-bucket-lifecycle-configuration-empty-fitler
r/s3_bucket_lifecycle_configuration: update value set in state for an empty `filter` argument
2 parents b6c1e18 + 481687f commit 5e0ac8d

File tree

5 files changed

+266
-11
lines changed

5 files changed

+266
-11
lines changed

.changelog/23232.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:bug
2+
resource/aws_s3_bucket_lifecycle_configuration: Prevent non-empty plans when `filter` is an empty configuration block
3+
```

internal/service/s3/bucket_lifecycle_configuration_test.go

+61
Original file line numberDiff line numberDiff line change
@@ -603,6 +603,32 @@ func TestAccS3BucketLifecycleConfiguration_TransitionUpdateBetweenDaysAndDate_in
603603
})
604604
}
605605

606+
// Reference: https://github.com/hashicorp/terraform-provider-aws/issues/23228
607+
func TestAccS3BucketLifecycleConfiguration_EmptyFilter_NonCurrentVersions(t *testing.T) {
608+
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
609+
resourceName := "aws_s3_bucket_lifecycle_configuration.test"
610+
611+
resource.ParallelTest(t, resource.TestCase{
612+
PreCheck: func() { acctest.PreCheck(t) },
613+
ErrorCheck: acctest.ErrorCheck(t, s3.EndpointsID),
614+
Providers: acctest.Providers,
615+
CheckDestroy: testAccCheckBucketLifecycleConfigurationDestroy,
616+
Steps: []resource.TestStep{
617+
{
618+
Config: testAccBucketLifecycleConfiguration_EmptyFilter_NonCurrentVersionsConfig(rName),
619+
Check: resource.ComposeTestCheckFunc(
620+
testAccCheckBucketLifecycleConfigurationExists(resourceName),
621+
),
622+
},
623+
{
624+
ResourceName: resourceName,
625+
ImportState: true,
626+
ImportStateVerify: true,
627+
},
628+
},
629+
})
630+
}
631+
606632
func testAccCheckBucketLifecycleConfigurationDestroy(s *terraform.State) error {
607633
conn := acctest.Provider.Meta().(*conns.AWSClient).S3Conn
608634

@@ -1111,3 +1137,38 @@ resource "aws_s3_bucket_lifecycle_configuration" "test" {
11111137
}
11121138
`, rName, transitionDate, storageClass)
11131139
}
1140+
1141+
func testAccBucketLifecycleConfiguration_EmptyFilter_NonCurrentVersionsConfig(rName string) string {
1142+
return fmt.Sprintf(`
1143+
resource "aws_s3_bucket" "test" {
1144+
bucket = %[1]q
1145+
}
1146+
1147+
resource "aws_s3_bucket_acl" "test" {
1148+
bucket = aws_s3_bucket.test.id
1149+
acl = "private"
1150+
}
1151+
1152+
resource "aws_s3_bucket_lifecycle_configuration" "test" {
1153+
bucket = aws_s3_bucket.test.bucket
1154+
1155+
rule {
1156+
id = %[1]q
1157+
1158+
filter {}
1159+
1160+
noncurrent_version_expiration {
1161+
newer_noncurrent_versions = 2
1162+
noncurrent_days = 30
1163+
}
1164+
1165+
noncurrent_version_transition {
1166+
noncurrent_days = 30
1167+
storage_class = "STANDARD_IA"
1168+
}
1169+
1170+
status = "Enabled"
1171+
}
1172+
}
1173+
`, rName)
1174+
}

internal/service/s3/flex.go

+1-9
Original file line numberDiff line numberDiff line change
@@ -886,14 +886,6 @@ func FlattenLifecycleRuleFilter(filter *s3.LifecycleRuleFilter) []interface{} {
886886
return nil
887887
}
888888

889-
if filter.And == nil &&
890-
filter.ObjectSizeGreaterThan == nil &&
891-
filter.ObjectSizeLessThan == nil &&
892-
(filter.Prefix == nil || aws.StringValue(filter.Prefix) == "") &&
893-
filter.Tag == nil {
894-
return nil
895-
}
896-
897889
m := make(map[string]interface{})
898890

899891
if filter.And != nil {
@@ -908,7 +900,7 @@ func FlattenLifecycleRuleFilter(filter *s3.LifecycleRuleFilter) []interface{} {
908900
m["object_size_less_than"] = int(aws.Int64Value(filter.ObjectSizeLessThan))
909901
}
910902

911-
if filter.Prefix != nil && aws.StringValue(filter.Prefix) != "" {
903+
if filter.Prefix != nil {
912904
m["prefix"] = aws.StringValue(filter.Prefix)
913905
}
914906

website/docs/guides/version-4-upgrade.html.md

+199
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,199 @@ your Terraform state and will henceforth be managed by Terraform.
467467

468468
Switch your Terraform configuration to the [`aws_s3_bucket_lifecycle_configuration` resource](/docs/providers/aws/r/s3_bucket_lifecycle_configuration.html) instead.
469469

470+
#### For Lifecycle Rules with no `prefix` previously configured
471+
472+
For example, given this previous configuration:
473+
474+
```terraform
475+
resource "aws_s3_bucket" "example" {
476+
bucket = "my-example-bucket"
477+
478+
lifecycle_rule {
479+
id = "Keep previous version 30 days, then in Glacier another 60"
480+
enabled = true
481+
482+
noncurrent_version_transition {
483+
days = 30
484+
storage_class = "GLACIER"
485+
}
486+
487+
noncurrent_version_expiration {
488+
days = 90
489+
}
490+
}
491+
492+
lifecycle_rule {
493+
id = "Delete old incomplete multi-part uploads"
494+
enabled = true
495+
abort_incomplete_multipart_upload_days = 7
496+
}
497+
}
498+
```
499+
500+
It will receive the following error after upgrading:
501+
502+
```
503+
│ Error: Value for unconfigurable attribute
504+
505+
│ with aws_s3_bucket.example,
506+
│ on main.tf line 1, in resource "aws_s3_bucket" "example":
507+
│ 1: resource "aws_s3_bucket" "example" {
508+
509+
│ Can't configure a value for "lifecycle_rule": its value will be decided automatically based on the result of applying this configuration.
510+
```
511+
512+
Since the `lifecycle_rule` argument changed to read-only, the recommendation is to update the configuration to use the `aws_s3_bucket_lifecycle_configuration`
513+
resource and remove any references to `lifecycle_rule` and its nested arguments in the `aws_s3_bucket` resource.
514+
515+
~> **Note:** When configuring the `rule.filter` configuration block in the new `aws_s3_bucket_lifecycle_configuration` resource, it is recommended to use the AWS CLI s3api [get-bucket-lifecycle-configuration](https://awscli.amazonaws.com/v2/documentation/api/latest/reference/s3api/get-bucket-lifecycle-configuration.html)
516+
to fetch the source bucket's lifecycle configuration and determine if the `Filter` is configured as `"Filter" : {}` or `"Filter" : { "Prefix": "" }`.
517+
If the former is returned, `rule.filter` should be configured as `filter {}`. If the latter is returned, `rule.filter` should be configured as follows.
518+
519+
```terraform
520+
resource "aws_s3_bucket" "example" {
521+
# ... other configuration ...
522+
}
523+
524+
resource "aws_s3_bucket_lifecycle_configuration" "example" {
525+
bucket = aws_s3_bucket.example.id
526+
527+
rule {
528+
id = "Keep previous version 30 days, then in Glacier another 60"
529+
status = "Enabled"
530+
531+
filter {
532+
prefix = ""
533+
}
534+
535+
noncurrent_version_transition {
536+
noncurrent_days = 30
537+
storage_class = "GLACIER"
538+
}
539+
540+
noncurrent_version_expiration {
541+
noncurrent_days = 90
542+
}
543+
}
544+
545+
rule {
546+
id = "Delete old incomplete multi-part uploads"
547+
status = "Enabled"
548+
549+
filter {
550+
prefix = ""
551+
}
552+
553+
abort_incomplete_multipart_upload {
554+
days_after_initiation = 7
555+
}
556+
}
557+
}
558+
```
559+
560+
It is then recommended running `terraform import` on each new resource to prevent data loss, e.g.
561+
562+
```shell
563+
$ terraform import aws_s3_bucket_lifecycle_configuration.example example
564+
aws_s3_bucket_lifecycle_configuration.example: Importing from ID "example"...
565+
aws_s3_bucket_lifecycle_configuration.example: Import prepared!
566+
Prepared aws_s3_bucket_lifecycle_configuration for import
567+
aws_s3_bucket_lifecycle_configuration.example: Refreshing state... [id=example]
568+
569+
Import successful!
570+
571+
The resources that were imported are shown above. These resources are now in
572+
your Terraform state and will henceforth be managed by Terraform.
573+
```
574+
575+
#### For Lifecycle Rules with `prefix` previously configured as an empty string
576+
577+
For example, given this configuration:
578+
579+
```terraform
580+
resource "aws_s3_bucket" "example" {
581+
bucket = "my-example-bucket"
582+
583+
lifecycle_rule {
584+
id = "log-expiration"
585+
enabled = true
586+
prefix = ""
587+
588+
transition {
589+
days = 30
590+
storage_class = "STANDARD_IA"
591+
}
592+
593+
transition {
594+
days = 180
595+
storage_class = "GLACIER"
596+
}
597+
}
598+
}
599+
```
600+
601+
It will receive the following error after upgrading:
602+
603+
```
604+
│ Error: Value for unconfigurable attribute
605+
606+
│ with aws_s3_bucket.example,
607+
│ on main.tf line 1, in resource "aws_s3_bucket" "example":
608+
│ 1: resource "aws_s3_bucket" "example" {
609+
610+
│ Can't configure a value for "lifecycle_rule": its value will be decided automatically based on the result of applying this configuration.
611+
```
612+
613+
Since the `lifecycle_rule` argument changed to read-only, the recommendation is to update the configuration to use the `aws_s3_bucket_lifecycle_configuration`
614+
resource and remove any references to `lifecycle_rule` and its nested arguments in the `aws_s3_bucket` resource:
615+
616+
```terraform
617+
resource "aws_s3_bucket" "example" {
618+
# ... other configuration ...
619+
}
620+
621+
resource "aws_s3_bucket_lifecycle_configuration" "example" {
622+
bucket = aws_s3_bucket.example.id
623+
624+
rule {
625+
id = "log-expiration"
626+
status = "Enabled"
627+
628+
filter {
629+
prefix = ""
630+
}
631+
632+
transition {
633+
days = 30
634+
storage_class = "STANDARD_IA"
635+
}
636+
637+
transition {
638+
days = 180
639+
storage_class = "GLACIER"
640+
}
641+
}
642+
}
643+
```
644+
645+
It is then recommended running `terraform import` on each new resource to prevent data loss, e.g.
646+
647+
```shell
648+
$ terraform import aws_s3_bucket_lifecycle_configuration.example example
649+
aws_s3_bucket_lifecycle_configuration.example: Importing from ID "example"...
650+
aws_s3_bucket_lifecycle_configuration.example: Import prepared!
651+
Prepared aws_s3_bucket_lifecycle_configuration for import
652+
aws_s3_bucket_lifecycle_configuration.example: Refreshing state... [id=example]
653+
654+
Import successful!
655+
656+
The resources that were imported are shown above. These resources are now in
657+
your Terraform state and will henceforth be managed by Terraform.
658+
```
659+
660+
#### For Lifecycle Rules with a `prefix`
661+
662+
470663
For example, given this previous configuration:
471664

472665
```terraform
@@ -476,18 +669,22 @@ resource "aws_s3_bucket" "example" {
476669
id = "log"
477670
enabled = true
478671
prefix = "log/"
672+
479673
tags = {
480674
rule = "log"
481675
autoclean = "true"
482676
}
677+
483678
transition {
484679
days = 30
485680
storage_class = "STANDARD_IA"
486681
}
682+
487683
transition {
488684
days = 60
489685
storage_class = "GLACIER"
490686
}
687+
491688
expiration {
492689
days = 90
493690
}
@@ -497,6 +694,7 @@ resource "aws_s3_bucket" "example" {
497694
id = "tmp"
498695
prefix = "tmp/"
499696
enabled = true
697+
500698
expiration {
501699
date = "2022-12-31"
502700
}
@@ -534,6 +732,7 @@ resource "aws_s3_bucket_lifecycle_configuration" "example" {
534732
filter {
535733
and {
536734
prefix = "log/"
735+
537736
tags = {
538737
rule = "log"
539738
autoclean = "true"

website/docs/r/s3_bucket_lifecycle_configuration.html.markdown

+2-2
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ The `rule` configuration block supports the following arguments:
151151

152152
* `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).
153153
* `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).
154-
* `filter` - (Optional) Configuration block used to identify objects that a Lifecycle Rule applies to [documented below](#filter).
154+
* `filter` - (Optional) Configuration block used to identify objects that a Lifecycle Rule applies to [documented below](#filter). If not specified, the `rule` will default to using `prefix`.
155155
* `id` - (Required) Unique identifier for the rule. The value cannot be longer than 255 characters.
156156
* `noncurrent_version_expiration` - (Optional) Configuration block that specifies when noncurrent object versions expire [documented below](#noncurrent_version_expiration).
157157
* `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).
158-
* `prefix` - (Optional) **DEPRECATED** Use `filter` instead. This has been deprecated by Amazon S3. Prefix identifying one or more objects to which the rule applies.
158+
* `prefix` - (Optional) **DEPRECATED** Use `filter` instead. This has been deprecated by Amazon S3. Prefix identifying one or more objects to which the rule applies. Defaults to an empty string (`""`) if `filter` is not specified.
159159
* `status` - (Required) Whether the rule is currently being applied. Valid values: `Enabled` or `Disabled`.
160160
* `transition` - (Optional) Set of configuration blocks that specify when an Amazon S3 object transitions to a specified storage class [documented below](#transition).
161161

0 commit comments

Comments
 (0)