Terraform-provider-aws: s3: Ignored lifecycle_policy is changed anyway

Created on 16 Jan 2019  ·  7Comments  ·  Source: hashicorp/terraform-provider-aws

Community Note

  • Please vote on this issue by adding a 👍 reaction to the original issue to help the community and maintainers prioritize this request
  • Please do not leave "+1" or "me too" comments, they generate extra noise for issue followers and do not help prioritize the request
  • If you are interested in working on this issue or have submitted a pull request, please leave a comment

Terraform Version

Terraform v0.11.11
+ provider.archive v1.1.0
+ provider.aws v1.30.0
+ provider.null v1.0.0
+ provider.template v1.0.0

Affected Resource(s)

  • aws_s3_bucket

Terraform Configuration Files

resource "aws_s3_bucket" "nexus" {
  bucket = "s3.${local.suffix}"
  acl    = "private"

  lifecycle {
    ignore_changes = ["lifecycle_rule"]
  }

  tags = "${merge(
              map("Name", "s3.${local.infix}",
                  "Environnement", var.environment,
                  "Stack", var.stack),
                  var.extra_tags)}"
}

Lifecycle policy as configured by Nexus:

{
"xmlns": "http://s3.amazonaws.com/doc/2006-03-01/","
Rule": {
  "Status": "Enabled",
  "Filter": {
    "Prefix": ""
  },
  "Tags": {
    "deleted" : "true"
  }
  "Expiration": {
    "Days": 3
  },
  "ID": "Expire soft-deleted blobstore objects"
  }
}

Debug Output

tfwrapper plan -- -target module.nexus-s3
INFO    tfwrapper : Getting state session
INFO    tfwrapper : Getting stack session

Warning: output "igw_id": must use splat syntax to access aws_internet_gateway.basic attribute "id", because it has "count" set; use aws_internet_gateway.basic.*.id to obtain a list of the attributes across all instances



Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

aws_s3_bucket.nexus: Refreshing state... (ID: s3.nexus)

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  ~ module.nexus-s3.aws_s3_bucket.nexus
      tags.Environment: "DEV" => "dev"


Plan: 0 to add, 1 to change, 0 to destroy.

Panic Output

Expected Behavior


We were changing bucket tags so only bucket tags should have changed.

Actual Behavior

Lifecycle policy was changed to:

{
"xmlns": "http://s3.amazonaws.com/doc/2006-03-01/","
Rule": {
  "Status": "Enabled",
  "Filter": {
    "Prefix": ""
  },
  "Expiration": {
    "Days": 3
  },
  "ID": "Expire soft-deleted blobstore objects"
  }
}

The "Tags": { "deleted" : "true" } filter was removed which led to all files in the bucket to be removed after 3 days. We were lucky this happened on a test env.

Steps to Reproduce

  1. Create bucket with terraform and ignore lifecycle change
  2. Manually set a lifecycle policy such as:
{
"xmlns": "http://s3.amazonaws.com/doc/2006-03-01/","
Rule": {
  "Status": "Enabled",
  "Filter": {
    "Prefix": ""
  },
  "Tags": {
    "deleted" : "true"
  }
  "Expiration": {
    "Days": 3
  },
  "ID": "Expire soft-deleted blobstore objects"
  }
}
  1. In terraform, change the set of tags for the bucket
  2. terraform plan only show changes to the bucket tags
  3. terraform apply change tags and lifecycle policy

Important Factoids

References

  • #0000
bug servics3

All 7 comments

I've reproduced this issue with this complete example:

terraform {
  required_version = "0.11.11"
}

provider "aws" {
  version = "1.56.0"
  region  = "us-east-1"
}

variable "environment" {
  default = "tf-test"
}

variable "stack" {
  default = "tf-test"
}

variable "extra_tags" {
  type    = "map"
  default = {}
}

resource "random_id" "suffix" {
  byte_length = 14
}

locals {
  suffix = "tf-test-pdecat-${random_id.suffix.hex}"
}

resource "aws_s3_bucket" "nexus" {
  bucket = "s3.${local.suffix}"
  acl    = "private"

  lifecycle {
    ignore_changes = ["lifecycle_rule"]
  }

  tags = "${merge(
              map("Name", "s3.${local.suffix}",
                  "Environnement", var.environment,
                  "Stack", var.stack),
                  var.extra_tags)}"
}

Steps:

  1. terraform init
  2. terraform apply
  3. edit the lifecycle rule of the bucket to add a filter on a single tag (but not prefix)
  4. terraform apply -auto-approve -var stack=tf-test-2
  5. notice that the filter on tags has disappeared

Note: when there is also a filter on prefix, the issue does not happen.

This is most certainly caused by a missing branch here:

            filter := lifecycleRule.Filter
            if filter != nil {
                if filter.And != nil {
                    // Prefix
                    if filter.And.Prefix != nil && *filter.And.Prefix != "" {
                        rule["prefix"] = *filter.And.Prefix
                    }
                    // Tag
                    if len(filter.And.Tags) > 0 {
                        rule["tags"] = tagsToMapS3(filter.And.Tags)
                    }
                } else {
                    // Prefix
                    if filter.Prefix != nil && *filter.Prefix != "" {
                        rule["prefix"] = *filter.Prefix
                    }
                }
            } else {
                if lifecycleRule.Prefix != nil {
                    rule["prefix"] = *lifecycleRule.Prefix
                }
            }

https://github.com/terraform-providers/terraform-provider-aws/blob/v1.56.0/aws/resource_aws_s3_bucket.go#L1027

Adding the following to the TestAccAWSS3Bucket_Lifecycle acceptance tests case:

diff --git a/aws/resource_aws_s3_bucket_test.go b/aws/resource_aws_s3_bucket_test.go
index 70683d6c5..e6c853688 100644
--- a/aws/resource_aws_s3_bucket_test.go
+++ b/aws/resource_aws_s3_bucket_test.go
@@ -824,6 +824,16 @@ func TestAccAWSS3Bucket_Lifecycle(t *testing.T) {
                        "aws_s3_bucket.bucket", "lifecycle_rule.3.tags.tagKey", "tagValue"),
                    resource.TestCheckResourceAttr(
                        "aws_s3_bucket.bucket", "lifecycle_rule.3.tags.terraform", "hashicorp"),
+                   resource.TestCheckResourceAttr(
+                       "aws_s3_bucket.bucket", "lifecycle_rule.4.id", "id5"),
+                   resource.TestCheckResourceAttr(
+                       "aws_s3_bucket.bucket", "lifecycle_rule.4.tags.tagKey", "tagValue"),
+                   resource.TestCheckResourceAttr(
+                       "aws_s3_bucket.bucket", "lifecycle_rule.4.tags.terraform", "hashicorp"),
+                   resource.TestCheckResourceAttr(
+                       "aws_s3_bucket.bucket", "lifecycle_rule.4.transition.460947558.days", "0"),
+                   resource.TestCheckResourceAttr(
+                       "aws_s3_bucket.bucket", "lifecycle_rule.4.transition.460947558.storage_class", "GLACIER"),
                ),
            },
            {
@@ -2381,6 +2391,20 @@ resource "aws_s3_bucket" "bucket" {
            date = "2016-01-12"
        }
    }
+   lifecycle_rule {
+       id = "id5"
+       enabled = true
+
+       tags = {
+           "tagKey" = "tagValue"
+           "terraform" = "hashicorp"
+       }
+
+       transition {
+           days = 0
+           storage_class = "GLACIER"
+       }
+   }
 }
 `, randInt)
 }

reveals that the provider assumes tag filters always come with a prefix filter and generates the following:

2019/01/16 18:43:38 [DEBUG] [aws-sdk-go] DEBUG: Request s3/PutBucketLifecycleConfiguration Details:
---[ REQUEST POST-SIGN ]-----------------------------
PUT /?lifecycle= HTTP/1.1
Host: tf-test-bucket-2576861885369114921.s3.us-west-2.amazonaws.com
User-Agent: aws-sdk-go/1.16.19 (go1.11.4; linux; amd64) APN/1.0 HashiCorp/1.0 Terraform/0.11.9-beta1
Content-Length: 1401
Authorization: AWS4-HMAC-SHA256 Credential=AK******/20190116/us-west-2/s3/aws4_request, SignedHeaders=content-length;content-md5;host;x-amz-content-sha256;x-amz-date, Signat
ure=*****
Content-Md5: *****==
X-Amz-Content-Sha256: *****
X-Amz-Date: 20190116T174338Z
Accept-Encoding: gzip

<LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><ID>id1</ID><Status>Enabled</Status><Transition><StorageClass>STANDARD_IA</StorageClass><Days>30</Days></Transition><Transition><Days>60</Days><StorageClass>INTELLIGENT_TIERING</StorageClass></Transition><Transition><Days>90</Days><StorageClass>ONEZONE_IA</StorageClass></Transition><Transition><Days>120</Days><StorageClass>GLACIER</StorageClass></Transition><Expiration><Days>365</Days></Expiration><Filter><Prefix>path1/</Prefix></Filter></Rule><Rule><Expiration><Date>2016-01-12T00:00:00Z</Date></Expiration><Filter><Prefix>path2/</Prefix></Filter><ID>id2</ID><Status>Enabled</Status></Rule><Rule><Filter><Prefix>path3/</Prefix></Filter><ID>id3</ID><Status>Enabled</Status><Transition><Days>0</Days><StorageClass>GLACIER</StorageClass></Transition></Rule><Rule><Expiration><Date>2016-01-12T00:00:00Z</Date></Expiration><Filter><And><Tag><Key>tagKey</Key><Value>tagValue</Value></Tag><Tag><Key>terraform</Key><Value>hashicorp</Value></Tag><Prefix>path4/</Prefix></And></Filter><ID>id4</ID><Status>Enabled</Status></Rule><Rule><Transition><Days>0</Days><StorageClass>GLACIER</StorageClass></Transition><Filter><And><Prefix></Prefix><Tag><Key>tagKey</Key><Value>tagValue</Value></Tag><Tag><Key>terraform</Key><Value>hashicorp</Value></Tag></And></Filter><ID>id5</ID><Status>Enabled</Status></Rule></LifecycleConfiguration>

Notice the empty <Prefix></Prefix> filter in the last rule.

While lifecycle rules created through the web console with filter on tags only are generated as:

2019-01-16T17:36:10.277+0100 [DEBUG] plugin.terraform-provider-aws_v1.56.0_x4: <LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><ID>test</ID><Filter><Tag><Key>test</Key><Value>test</Value></Tag></Filter><Status>Enabled</Status><Transition><Days>30</Days><StorageClass>STANDARD_IA</StorageClass></Transition></Rule></LifecycleConfiguration>

Note that this only happens if there is a single tag in the filter!

When there are two or more tags in the filter, the web console adds empty prefixes and generates the following;

2019-01-16T19:25:36.808+0100 [DEBUG] plugin.terraform-provider-aws_v1.56.0_x4: <LifecycleConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Rule><ID>test</ID><Filter><And><Prefix></Prefix><Tag><Key>testtag1</Key><Value>testtag1</Value></Tag><Tag><Key>testtag2</Key><Value>testtag2</Value></Tag></And></Filter><Status>Enabled</Status><Transition><Days>30</Days><StorageClass>STANDARD_IA</StorageClass></Transition></Rule></LifecycleConfiguration>

Work-around: adding a second tag to filter or a prefix.

This has been released in version 2.19.0 of the Terraform AWS provider. Please see the Terraform documentation on provider versioning or reach out if you need any assistance upgrading.

For further feature requests or bug reports with this functionality, please create a new GitHub issue following the template for triage. Thanks!

I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues.

If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. Thanks!

Was this page helpful?
0 / 5 - 0 ratings