Skip to content

Commit

Permalink
Chore/000 add lifecycle config (#15)
Browse files Browse the repository at this point in the history
* add lifecycle config

* amend ref names

* amend filtering

* amend filtering

* add on to readme

* revert object ownership change

* amend README for lifecycle config

---------

Co-authored-by: liyin <[email protected]>
  • Loading branch information
liyin00 and liyin authored Nov 25, 2024
1 parent 66b6a1a commit e99a014
Show file tree
Hide file tree
Showing 4 changed files with 218 additions and 8 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ module "s3-generic" {
|------|-------------|------|---------|:--------:|
| <a name="input_force_destroy"></a> [force\_destroy](#input\_force\_destroy) | When destroying this user, destroy even if it has non-Terraform-managed IAM access keys, login profile or MFA devices. Without force\_destroy a user with non-Terraform-managed access keys and login profile will fail to be destroyed. | `bool` | `false` | no |
| <a name="input_path"></a> [path](#input\_path) | Desired path for the IAM user | `string` | `"/"` | no |
| <a name="input_s3_buckets"></a> [s3\_buckets](#input\_s3\_buckets) | A map of bucket names to an object describing the S3 bucket settings for the bucket. | <pre>map(object({<br/> bucket = string<br/> permissions_boundary = string<br/> region = string<br/> acl = optional(string)<br/> log_bucket_for_s3 = optional(string)<br/> policies = list(string)<br/> server_side_encryption_configuration = any<br/> cors_configuration = optional(<br/> list(<br/> object({<br/> allowed_methods = list(string)<br/> allowed_origins = list(string)<br/> allowed_headers = optional(list(string))<br/> expose_headers = optional(list(string))<br/> max_age_seconds = optional(number)<br/> id = optional(string)<br/> })<br/> )<br/> )<br/> }))</pre> | <pre>{<br/> "main": {<br/> "bucket": "",<br/> "log_bucket_for_s3": "",<br/> "permissions_boundary": "",<br/> "policies": [],<br/> "region": "ap-southeast-1",<br/> "server_side_encryption_configuration": {<br/> "rule": {<br/> "apply_server_side_encryption_by_default": {<br/> "sse_algorithm": "AES256"<br/> }<br/> }<br/> }<br/> }<br/>}</pre> | no |
| <a name="input_s3_buckets"></a> [s3\_buckets](#input\_s3\_buckets) | A map of bucket names to an object describing the S3 bucket settings for the bucket. | <pre>map(object({ </br> bucket = string </br> permissions_boundary = string </br> region = string </br> acl = optional(string) </br> log_bucket_for_s3 = optional(string) </br> policies = list(string) </br> server_side_encryption_configuration = any </br> cors_configuration = optional( </br> list( </br> object({ </br> allowed_methods = list(string) </br> allowed_origins = list(string) </br> allowed_headers = optional(list(string)) </br> expose_headers = optional(list(string)) </br> max_age_seconds = optional(number) </br> id = optional(string) </br> }) </br> ) </br> ) </br> lifecycle_rules = optional(list(object({ </br> id = optional(string) </br> enabled = optional(bool, true) </br> filter = optional(object({ </br> prefix = optional(string) </br> object_size_greater_than = optional(number) </br> object_size_less_than = optional(number) </br> tags = optional(map(string)) </br> })) </br> transition = optional(list(object({ </br> days = optional(number) </br> date = optional(string) </br> storage_class = string </br> }))) </br> }))) </br> })) </br></pre> | no |
| <a name="input_tags"></a> [tags](#input\_tags) | (Optional) A mapping of tags to assign to the bucket. | `map(string)` | `{}` | no |

## Outputs
Expand Down
43 changes: 43 additions & 0 deletions examples/full/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,49 @@ module "s3-generic" {
}
}
}
lifecycle_rules = [
{
id = "backup-lifecycle-rule"
enabled = true
filter = {
object_size_greater_than = 0
}
transition = [
{
days = 30
storage_class = "STANDARD_IA"
},
{
days = 60
storage_class = "GLACIER"
},
{
days = 150
storage_class = "DEEP_ARCHIVE"
}
]
noncurrent_version_transition = [
{
noncurrent_days = 30
storage_class = "STANDARD_IA"
},
{
noncurrent_days = 60
storage_class = "GLACIER"
},
{
noncurrent_days = 150
storage_class = "DEEP_ARCHIVE"
}
]
expiration = {
days = 183
}
noncurrent_version_expiration = {
noncurrent_days = 151
}
}
]
}
}
}
152 changes: 152 additions & 0 deletions main.tf
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "main" {
for_each = var.s3_buckets
Expand Down Expand Up @@ -208,3 +209,154 @@ resource "aws_iam_role_policy" "main" {
]
})
}

###################################################################
# Lifecycle Config
###################################################################

resource "aws_s3_bucket_lifecycle_configuration" "main" {
# Only create lifecycle rules for buckets that have them defined
for_each = {
for key, value in var.s3_buckets : key => value
if try(length(value.lifecycle_rules), 0) > 0
}

bucket = each.value.bucket
expected_bucket_owner = data.aws_caller_identity.current.account_id

dynamic "rule" {
for_each = each.value.lifecycle_rules

content {
id = coalesce(try(rule.value.id, null), "rule-${rule.key}")
status = coalesce(
try(rule.value.enabled ? "Enabled" : "Disabled", null),
try(tobool(rule.value.status) ? "Enabled" : "Disabled", null),
try(title(lower(rule.value.status)), null),
"Enabled"
)

# Abort incomplete multipart uploads
dynamic "abort_incomplete_multipart_upload" {
for_each = try(rule.value.abort_incomplete_multipart_upload_days != null ? [rule.value.abort_incomplete_multipart_upload_days] : [], [])

content {
days_after_initiation = abort_incomplete_multipart_upload.value
}
}

# Object expiration rules
dynamic "expiration" {
for_each = try(rule.value.expiration != null ? [rule.value.expiration] : [], [])

content {
date = try(expiration.value.date, null)
days = try(expiration.value.days, null)
expired_object_delete_marker = try(expiration.value.expired_object_delete_marker, null)
}
}

# Object transition rules
dynamic "transition" {
for_each = try(rule.value.transition != null ? rule.value.transition : [], [])

content {
date = try(transition.value.date, null)
days = try(transition.value.days, null)
storage_class = transition.value.storage_class
}
}

# Non-current version expiration
dynamic "noncurrent_version_expiration" {
for_each = try(rule.value.noncurrent_version_expiration != null ? [rule.value.noncurrent_version_expiration] : [], [])

content {
newer_noncurrent_versions = try(noncurrent_version_expiration.value.newer_noncurrent_versions, null)
noncurrent_days = coalesce(
try(noncurrent_version_expiration.value.days, null),
try(noncurrent_version_expiration.value.noncurrent_days, null)
)
}
}

# Non-current version transition
dynamic "noncurrent_version_transition" {
for_each = try(rule.value.noncurrent_version_transition != null ? rule.value.noncurrent_version_transition : [], [])

content {
newer_noncurrent_versions = try(noncurrent_version_transition.value.newer_noncurrent_versions, null)
noncurrent_days = coalesce(
try(noncurrent_version_transition.value.days, null),
try(noncurrent_version_transition.value.noncurrent_days, null)
)
storage_class = noncurrent_version_transition.value.storage_class
}
}

# Empty filter
dynamic "filter" {
for_each = length(try(flatten([rule.value.filter]), [])) == 0 ? [true] : []

content {
}
}

# Single condition filter (no "and" block needed)
dynamic "filter" {
for_each = [for v in try(flatten([rule.value.filter]), []) : v if(
length(compact([
try(v.prefix, null),
try(v.object_size_greater_than, null),
try(v.object_size_less_than, null)
])) <= 1 &&
length(try(flatten([v.tags, v.tag]), [])) <= 1
)]

content {
object_size_greater_than = try(filter.value.object_size_greater_than, null)
object_size_less_than = try(filter.value.object_size_less_than, null)
prefix = try(filter.value.prefix, null)

dynamic "tag" {
for_each = try(flatten([filter.value.tags, filter.value.tag]), [])
content {
key = try(tag.value.key, null)
value = try(tag.value.value, null)
}
}
}
}

# Multiple conditions filter (requires "and" block)
dynamic "filter" {
for_each = [for v in try(flatten([rule.value.filter]), []) : v if(
length(compact([
try(v.prefix, null),
try(v.object_size_greater_than, null),
try(v.object_size_less_than, null)
])) > 1 ||
length(try(flatten([v.tags, v.tag]), [])) > 1
)]

content {
and {
object_size_greater_than = try(filter.value.object_size_greater_than, null)
object_size_less_than = try(filter.value.object_size_less_than, null)
prefix = try(filter.value.prefix, null)
tags = try(filter.value.tags, filter.value.tag, null)
}
}
}
}
}

depends_on = [aws_s3_bucket_versioning.main]

lifecycle {
precondition {
condition = can(aws_s3_bucket_versioning.main[each.key].versioning_configuration[0].status == "Enabled")
error_message = "S3 bucket versioning must be enabled to use lifecycle rules with version-specific actions."
}
}
}
29 changes: 22 additions & 7 deletions variables.tf
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
variable "s3_buckets" {
description = "A map of bucket names to an object describing the S3 bucket settings for the bucket."
type = map(object({
type = map(object({
bucket = string
permissions_boundary = string
region = string
acl = optional(string)
log_bucket_for_s3 = optional(string)
policies = list(string)
server_side_encryption_configuration = any
cors_configuration = optional(
cors_configuration = optional(
list(
object({
allowed_methods = list(string)
Expand All @@ -20,15 +20,30 @@ variable "s3_buckets" {
})
)
)
lifecycle_rules = optional(list(object({
id = optional(string)
enabled = optional(bool, true)
filter = optional(object({
prefix = optional(string)
object_size_greater_than = optional(number)
object_size_less_than = optional(number)
tags = optional(map(string))
}))
transition = optional(list(object({
days = optional(number)
date = optional(string)
storage_class = string
})))
})))
}))

default = {
main = {
bucket = ""
region = "ap-southeast-1"
permissions_boundary = ""
log_bucket_for_s3 = ""
policies = []
bucket = ""
region = "ap-southeast-1"
permissions_boundary = ""
log_bucket_for_s3 = ""
policies = []
server_side_encryption_configuration = {
rule = {
apply_server_side_encryption_by_default = {
Expand Down

0 comments on commit e99a014

Please sign in to comment.