From d8ebed8012e65a2cf1514b59ad2f3c11d7422764 Mon Sep 17 00:00:00 2001
From: Maeghan Porter <80358403+maeghan-porter@users.noreply.github.com>
Date: Fri, 9 Apr 2021 15:06:27 -0300
Subject: [PATCH] Add support for multiple lifecycle rules (#85)
* Adding support for multiple lifecycle rules
* Run fmt
* Auto Format
* As requested, making one default lifecycle with same values as old variables
* As requested, making one default lifecycle with same values as old variables
* As requested, making one default lifecycle with same values as old variables
* fix typo
* Auto Format
* fix test
* Add bridgecrew exception line
* Ran go fmt on test and updated bridgecrew comment as advised.
* Trigger validate-codeowners
Co-authored-by: cloudpossebot <11232728+cloudpossebot@users.noreply.github.com>
---
.github/CODEOWNERS | 4 +-
.github/workflows/validate-codeowners.yml | 2 +
README.md | 15 +--
docs/terraform.md | 15 +--
examples/complete/lifecycle.us-west-1.tfvars | 60 ++++++++++
examples/complete/main.tf | 1 +
examples/complete/variables.tf | 45 +++++++
main.tf | 91 ++++++++-------
test/src/examples_complete_test.go | 44 ++++++-
variables.tf | 117 +++++++------------
10 files changed, 241 insertions(+), 153 deletions(-)
create mode 100644 examples/complete/lifecycle.us-west-1.tfvars
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
index 2537f2f8..6f64b5a3 100644
--- a/.github/CODEOWNERS
+++ b/.github/CODEOWNERS
@@ -15,8 +15,8 @@
# Cloud Posse must review any changes to standard context definition,
# but some changes can be rubber-stamped.
-**/*.tf @cloudposse/engineering @cloudposse/approvers
-README.yaml @cloudposse/engineering @cloudposse/approvers
+**/*.tf @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers
+README.yaml @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers
README.md @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers
docs/*.md @cloudposse/engineering @cloudposse/contributors @cloudposse/approvers
diff --git a/.github/workflows/validate-codeowners.yml b/.github/workflows/validate-codeowners.yml
index 386eb286..c5193b62 100644
--- a/.github/workflows/validate-codeowners.yml
+++ b/.github/workflows/validate-codeowners.yml
@@ -1,5 +1,7 @@
name: Validate Codeowners
on:
+ workflow_dispatch:
+
pull_request:
jobs:
diff --git a/README.md b/README.md
index 8d0ec745..58abfe46 100644
--- a/README.md
+++ b/README.md
@@ -224,17 +224,10 @@ Available targets:
| [bucket\_name](#input\_bucket\_name) | Bucket name. If provided, the bucket will be created with this name instead of generating the name from the context | `string` | `null` | no |
| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` |
{
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {}
}
| no |
| [cors\_rule\_inputs](#input\_cors\_rule\_inputs) | Specifies the allowed headers, methods, origins and exposed headers when using CORS on this bucket | list(object({
allowed_headers = list(string)
allowed_methods = list(string)
allowed_origins = list(string)
expose_headers = list(string)
max_age_seconds = number
}))
| `null` | no |
-| [deeparchive\_transition\_days](#input\_deeparchive\_transition\_days) | Number of days after which to move the data to the glacier deep archive storage tier | `number` | `90` | no |
| [delimiter](#input\_delimiter) | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
-| [enable\_current\_object\_expiration](#input\_enable\_current\_object\_expiration) | Enables the expiration of current objects | `bool` | `true` | no |
-| [enable\_deeparchive\_transition](#input\_enable\_deeparchive\_transition) | Enables the transition to AWS Glacier Deep Archive which can cause unnecessary costs for huge amount of small files | `bool` | `false` | no |
-| [enable\_glacier\_transition](#input\_enable\_glacier\_transition) | Enables the transition to AWS Glacier which can cause unnecessary costs for huge amount of small files | `bool` | `true` | no |
-| [enable\_standard\_ia\_transition](#input\_enable\_standard\_ia\_transition) | Enables the transition to STANDARD\_IA | `bool` | `false` | no |
| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| [environment](#input\_environment) | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
-| [expiration\_days](#input\_expiration\_days) | Number of days after which to expunge the objects | `number` | `90` | no |
| [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no |
-| [glacier\_transition\_days](#input\_glacier\_transition\_days) | Number of days after which to move the data to the glacier storage tier | `number` | `60` | no |
| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. | list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no |
| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for default, which is `0`.
Does not affect `id_full`. | `number` | `null` | no |
| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no |
@@ -242,17 +235,12 @@ Available targets:
| [label\_key\_case](#input\_label\_key\_case) | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no |
| [label\_order](#input\_label\_order) | The naming order of the id output and Name tag.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no |
| [label\_value\_case](#input\_label\_value\_case) | The letter case of output label values (also used in `tags` and `id`).
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Default value: `lower`. | `string` | `null` | no |
-| [lifecycle\_rule\_enabled](#input\_lifecycle\_rule\_enabled) | Enable or disable lifecycle rule | `bool` | `false` | no |
-| [lifecycle\_tags](#input\_lifecycle\_tags) | Tags filter. Used to manage object lifecycle events | `map(string)` | `{}` | no |
+| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules | list(object({
prefix = string
enabled = bool
tags = map(string)
enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number
standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
| [
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no |
| [logging](#input\_logging) | Bucket access logging configuration. | object({
bucket_name = string
prefix = string
})
| `null` | no |
| [name](#input\_name) | Solution name, e.g. 'app' or 'jenkins' | `string` | `null` | no |
| [namespace](#input\_namespace) | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no |
-| [noncurrent\_version\_deeparchive\_transition\_days](#input\_noncurrent\_version\_deeparchive\_transition\_days) | Number of days to persist in the standard storage tier before moving to the glacier deeparchive access tier | `number` | `60` | no |
-| [noncurrent\_version\_expiration\_days](#input\_noncurrent\_version\_expiration\_days) | Specifies when noncurrent object versions expire | `number` | `90` | no |
-| [noncurrent\_version\_glacier\_transition\_days](#input\_noncurrent\_version\_glacier\_transition\_days) | Number of days to persist in the standard storage tier before moving to the glacier infrequent access tier | `number` | `30` | no |
| [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. | object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no |
| [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no |
-| [prefix](#input\_prefix) | Prefix identifying one or more objects to which the rule applies | `string` | `""` | no |
| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| [replication\_rules](#input\_replication\_rules) | Specifies the replication rules if S3 bucket replication is enabled | `list(any)` | `null` | no |
| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Set to `false` to disable the restricting of making the bucket public | `bool` | `true` | no |
@@ -260,7 +248,6 @@ Available targets:
| [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replica_bucket_arn` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no |
| [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no |
| [stage](#input\_stage) | Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
-| [standard\_transition\_days](#input\_standard\_transition\_days) | Number of days to persist in the standard storage tier before moving to the infrequent access tier | `number` | `30` | no |
| [tags](#input\_tags) | Additional tags (e.g. `map('BusinessUnit','XYZ')` | `map(string)` | `{}` | no |
| [user\_enabled](#input\_user\_enabled) | Set to `true` to create an IAM user with permission to access the bucket | `bool` | `false` | no |
| [versioning\_enabled](#input\_versioning\_enabled) | A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket | `bool` | `true` | no |
diff --git a/docs/terraform.md b/docs/terraform.md
index 6da65e76..1d2274ea 100644
--- a/docs/terraform.md
+++ b/docs/terraform.md
@@ -51,17 +51,10 @@
| [bucket\_name](#input\_bucket\_name) | Bucket name. If provided, the bucket will be created with this name instead of generating the name from the context | `string` | `null` | no |
| [context](#input\_context) | Single object for setting entire context at once.
See description of individual variables for details.
Leave string and numeric variables as `null` to use default value.
Individual variable settings (non-null) override settings in context object,
except for attributes, tags, and additional\_tag\_map, which are merged. | `any` | {
"additional_tag_map": {},
"attributes": [],
"delimiter": null,
"enabled": true,
"environment": null,
"id_length_limit": null,
"label_key_case": null,
"label_order": [],
"label_value_case": null,
"name": null,
"namespace": null,
"regex_replace_chars": null,
"stage": null,
"tags": {}
}
| no |
| [cors\_rule\_inputs](#input\_cors\_rule\_inputs) | Specifies the allowed headers, methods, origins and exposed headers when using CORS on this bucket | list(object({
allowed_headers = list(string)
allowed_methods = list(string)
allowed_origins = list(string)
expose_headers = list(string)
max_age_seconds = number
}))
| `null` | no |
-| [deeparchive\_transition\_days](#input\_deeparchive\_transition\_days) | Number of days after which to move the data to the glacier deep archive storage tier | `number` | `90` | no |
| [delimiter](#input\_delimiter) | Delimiter to be used between `namespace`, `environment`, `stage`, `name` and `attributes`.
Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. | `string` | `null` | no |
-| [enable\_current\_object\_expiration](#input\_enable\_current\_object\_expiration) | Enables the expiration of current objects | `bool` | `true` | no |
-| [enable\_deeparchive\_transition](#input\_enable\_deeparchive\_transition) | Enables the transition to AWS Glacier Deep Archive which can cause unnecessary costs for huge amount of small files | `bool` | `false` | no |
-| [enable\_glacier\_transition](#input\_enable\_glacier\_transition) | Enables the transition to AWS Glacier which can cause unnecessary costs for huge amount of small files | `bool` | `true` | no |
-| [enable\_standard\_ia\_transition](#input\_enable\_standard\_ia\_transition) | Enables the transition to STANDARD\_IA | `bool` | `false` | no |
| [enabled](#input\_enabled) | Set to false to prevent the module from creating any resources | `bool` | `null` | no |
| [environment](#input\_environment) | Environment, e.g. 'uw2', 'us-west-2', OR 'prod', 'staging', 'dev', 'UAT' | `string` | `null` | no |
-| [expiration\_days](#input\_expiration\_days) | Number of days after which to expunge the objects | `number` | `90` | no |
| [force\_destroy](#input\_force\_destroy) | A boolean string that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. These objects are not recoverable | `bool` | `false` | no |
-| [glacier\_transition\_days](#input\_glacier\_transition\_days) | Number of days after which to move the data to the glacier storage tier | `number` | `60` | no |
| [grants](#input\_grants) | An ACL policy grant. Conflicts with `acl`. Set `acl` to `null` to use this. | list(object({
id = string
type = string
permissions = list(string)
uri = string
}))
| `null` | no |
| [id\_length\_limit](#input\_id\_length\_limit) | Limit `id` to this many characters (minimum 6).
Set to `0` for unlimited length.
Set to `null` for default, which is `0`.
Does not affect `id_full`. | `number` | `null` | no |
| [ignore\_public\_acls](#input\_ignore\_public\_acls) | Set to `false` to disable the ignoring of public access lists on the bucket | `bool` | `true` | no |
@@ -69,17 +62,12 @@
| [label\_key\_case](#input\_label\_key\_case) | The letter case of label keys (`tag` names) (i.e. `name`, `namespace`, `environment`, `stage`, `attributes`) to use in `tags`.
Possible values: `lower`, `title`, `upper`.
Default value: `title`. | `string` | `null` | no |
| [label\_order](#input\_label\_order) | The naming order of the id output and Name tag.
Defaults to ["namespace", "environment", "stage", "name", "attributes"].
You can omit any of the 5 elements, but at least one must be present. | `list(string)` | `null` | no |
| [label\_value\_case](#input\_label\_value\_case) | The letter case of output label values (also used in `tags` and `id`).
Possible values: `lower`, `title`, `upper` and `none` (no transformation).
Default value: `lower`. | `string` | `null` | no |
-| [lifecycle\_rule\_enabled](#input\_lifecycle\_rule\_enabled) | Enable or disable lifecycle rule | `bool` | `false` | no |
-| [lifecycle\_tags](#input\_lifecycle\_tags) | Tags filter. Used to manage object lifecycle events | `map(string)` | `{}` | no |
+| [lifecycle\_rules](#input\_lifecycle\_rules) | A list of lifecycle rules | list(object({
prefix = string
enabled = bool
tags = map(string)
enable_glacier_transition = bool
enable_deeparchive_transition = bool
enable_standard_ia_transition = bool
enable_current_object_expiration = bool
abort_incomplete_multipart_upload_days = number
noncurrent_version_glacier_transition_days = number
noncurrent_version_deeparchive_transition_days = number
noncurrent_version_expiration_days = number
standard_transition_days = number
glacier_transition_days = number
deeparchive_transition_days = number
expiration_days = number
}))
| [
{
"abort_incomplete_multipart_upload_days": 90,
"deeparchive_transition_days": 90,
"enable_current_object_expiration": true,
"enable_deeparchive_transition": false,
"enable_glacier_transition": true,
"enable_standard_ia_transition": false,
"enabled": false,
"expiration_days": 90,
"glacier_transition_days": 60,
"noncurrent_version_deeparchive_transition_days": 60,
"noncurrent_version_expiration_days": 90,
"noncurrent_version_glacier_transition_days": 30,
"prefix": "",
"standard_transition_days": 30,
"tags": {}
}
]
| no |
| [logging](#input\_logging) | Bucket access logging configuration. | object({
bucket_name = string
prefix = string
})
| `null` | no |
| [name](#input\_name) | Solution name, e.g. 'app' or 'jenkins' | `string` | `null` | no |
| [namespace](#input\_namespace) | Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp' | `string` | `null` | no |
-| [noncurrent\_version\_deeparchive\_transition\_days](#input\_noncurrent\_version\_deeparchive\_transition\_days) | Number of days to persist in the standard storage tier before moving to the glacier deeparchive access tier | `number` | `60` | no |
-| [noncurrent\_version\_expiration\_days](#input\_noncurrent\_version\_expiration\_days) | Specifies when noncurrent object versions expire | `number` | `90` | no |
-| [noncurrent\_version\_glacier\_transition\_days](#input\_noncurrent\_version\_glacier\_transition\_days) | Number of days to persist in the standard storage tier before moving to the glacier infrequent access tier | `number` | `30` | no |
| [object\_lock\_configuration](#input\_object\_lock\_configuration) | A configuration for S3 object locking. With S3 Object Lock, you can store objects using a `write once, read many` (WORM) model. Object Lock can help prevent objects from being deleted or overwritten for a fixed amount of time or indefinitely. | object({
mode = string # Valid values are GOVERNANCE and COMPLIANCE.
days = number
years = number
})
| `null` | no |
| [policy](#input\_policy) | A valid bucket policy JSON document. Note that if the policy document is not specific enough (but still valid), Terraform may view the policy as constantly changing in a terraform plan. In this case, please make sure you use the verbose/specific version of the policy | `string` | `""` | no |
-| [prefix](#input\_prefix) | Prefix identifying one or more objects to which the rule applies | `string` | `""` | no |
| [regex\_replace\_chars](#input\_regex\_replace\_chars) | Regex to replace chars with empty string in `namespace`, `environment`, `stage` and `name`.
If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. | `string` | `null` | no |
| [replication\_rules](#input\_replication\_rules) | Specifies the replication rules if S3 bucket replication is enabled | `list(any)` | `null` | no |
| [restrict\_public\_buckets](#input\_restrict\_public\_buckets) | Set to `false` to disable the restricting of making the bucket public | `bool` | `true` | no |
@@ -87,7 +75,6 @@
| [s3\_replication\_enabled](#input\_s3\_replication\_enabled) | Set this to true and specify `s3_replica_bucket_arn` to enable replication. `versioning_enabled` must also be `true`. | `bool` | `false` | no |
| [sse\_algorithm](#input\_sse\_algorithm) | The server-side encryption algorithm to use. Valid values are `AES256` and `aws:kms` | `string` | `"AES256"` | no |
| [stage](#input\_stage) | Stage, e.g. 'prod', 'staging', 'dev', OR 'source', 'build', 'test', 'deploy', 'release' | `string` | `null` | no |
-| [standard\_transition\_days](#input\_standard\_transition\_days) | Number of days to persist in the standard storage tier before moving to the infrequent access tier | `number` | `30` | no |
| [tags](#input\_tags) | Additional tags (e.g. `map('BusinessUnit','XYZ')` | `map(string)` | `{}` | no |
| [user\_enabled](#input\_user\_enabled) | Set to `true` to create an IAM user with permission to access the bucket | `bool` | `false` | no |
| [versioning\_enabled](#input\_versioning\_enabled) | A state of versioning. Versioning is a means of keeping multiple variants of an object in the same bucket | `bool` | `true` | no |
diff --git a/examples/complete/lifecycle.us-west-1.tfvars b/examples/complete/lifecycle.us-west-1.tfvars
new file mode 100644
index 00000000..df9e7ac2
--- /dev/null
+++ b/examples/complete/lifecycle.us-west-1.tfvars
@@ -0,0 +1,60 @@
+region = "us-west-1"
+
+namespace = "eg"
+
+stage = "test"
+
+name = "s3-lifecycle-test"
+
+acl = "private"
+
+lifecycle_rules = [
+ {
+ prefix = null
+ enabled = true
+ tags = { "temp" : "true" }
+
+ enable_glacier_transition = false
+ enable_deeparchive_transition = false
+ enable_standard_ia_transition = false
+ enable_current_object_expiration = true
+
+ abort_incomplete_multipart_upload_days = null
+ noncurrent_version_glacier_transition_days = 0
+ noncurrent_version_deeparchive_transition_days = 0
+ noncurrent_version_expiration_days = 30
+
+ standard_transition_days = 0
+ glacier_transition_days = 0
+ deeparchive_transition_days = 0
+ expiration_days = 1
+ },
+ {
+ prefix = null
+ enabled = true
+ tags = {}
+
+ enable_glacier_transition = false
+ enable_deeparchive_transition = false
+ enable_standard_ia_transition = false
+ enable_current_object_expiration = true
+
+ abort_incomplete_multipart_upload_days = 1
+ noncurrent_version_glacier_transition_days = 0
+ noncurrent_version_deeparchive_transition_days = 0
+ noncurrent_version_expiration_days = 30
+
+ standard_transition_days = 0
+ glacier_transition_days = 0
+ deeparchive_transition_days = 0
+ expiration_days = 30
+ }
+]
+
+force_destroy = true
+
+versioning_enabled = false
+
+allow_encrypted_uploads_only = true
+
+allowed_bucket_actions = ["s3:PutObject", "s3:PutObjectAcl", "s3:GetObject", "s3:DeleteObject", "s3:ListBucket", "s3:ListBucketMultipartUploads", "s3:GetBucketLocation", "s3:AbortMultipartUpload"]
diff --git a/examples/complete/main.tf b/examples/complete/main.tf
index 59e99856..d06ae037 100644
--- a/examples/complete/main.tf
+++ b/examples/complete/main.tf
@@ -9,6 +9,7 @@ module "s3_bucket" {
acl = var.acl
force_destroy = var.force_destroy
grants = var.grants
+ lifecycle_rules = var.lifecycle_rules
versioning_enabled = var.versioning_enabled
allow_encrypted_uploads_only = var.allow_encrypted_uploads_only
allowed_bucket_actions = var.allowed_bucket_actions
diff --git a/examples/complete/variables.tf b/examples/complete/variables.tf
index 4748cf74..21562d42 100644
--- a/examples/complete/variables.tf
+++ b/examples/complete/variables.tf
@@ -16,6 +16,51 @@ variable "grants" {
description = "A list of ACL policy grants. Conflicts with `acl`. Set `acl` to `null` to use this."
}
+variable "lifecycle_rules" {
+ type = list(object({
+ prefix = string
+ enabled = bool
+ tags = map(string)
+
+ enable_glacier_transition = bool
+ enable_deeparchive_transition = bool
+ enable_standard_ia_transition = bool
+ enable_current_object_expiration = bool
+
+ abort_incomplete_multipart_upload_days = number
+ noncurrent_version_glacier_transition_days = number
+ noncurrent_version_deeparchive_transition_days = number
+ noncurrent_version_expiration_days = number
+
+ standard_transition_days = number
+ glacier_transition_days = number
+ deeparchive_transition_days = number
+ expiration_days = number
+ }))
+ default = [{
+ enabled = false
+ prefix = ""
+ tags = {}
+
+ enable_glacier_transition = true
+ enable_deeparchive_transition = false
+ enable_standard_ia_transition = false
+ enable_current_object_expiration = true
+
+ abort_incomplete_multipart_upload_days = 90
+ noncurrent_version_glacier_transition_days = 30
+ noncurrent_version_deeparchive_transition_days = 60
+ noncurrent_version_expiration_days = 90
+
+ standard_transition_days = 30
+ glacier_transition_days = 60
+ deeparchive_transition_days = 90
+ expiration_days = 90
+ }]
+
+ description = "A list of lifecycle rules."
+}
+
variable "policy" {
type = string
default = ""
diff --git a/main.tf b/main.tf
index 130a4a74..4de26537 100644
--- a/main.tf
+++ b/main.tf
@@ -3,8 +3,9 @@ locals {
}
resource "aws_s3_bucket" "default" {
- #bridgecrew:skip=BC_AWS_S3_13:Skipping `Enable S3 Bucket Logging` check until bridgecrew will support dynamic blocks (https://github.com/bridgecrewio/checkov/issues/776).
+ #bridgecrew:skip=BC_AWS_S3_13:Skipping `Enable S3 Bucket Logging` because we do not have good defaults
#bridgecrew:skip=CKV_AWS_52:Skipping `Ensure S3 bucket has MFA delete enabled` due to issue in terraform (https://github.com/hashicorp/terraform-provider-aws/issues/629).
+ #bridgecrew:skip=BC_AWS_S3_16:Skipping `Ensure S3 bucket versioning is enabled` because dynamic blocks are not supported by checkov
count = module.this.enabled ? 1 : 0
bucket = local.bucket_name
acl = try(length(var.grants), 0) == 0 ? var.acl : null
@@ -16,69 +17,71 @@ resource "aws_s3_bucket" "default" {
enabled = var.versioning_enabled
}
- lifecycle_rule {
- id = module.this.id
- enabled = var.lifecycle_rule_enabled
- prefix = var.prefix
- tags = var.lifecycle_tags
- abort_incomplete_multipart_upload_days = var.abort_incomplete_multipart_upload_days
+ dynamic "lifecycle_rule" {
+ for_each = var.lifecycle_rules
+ content {
+ enabled = lifecycle_rule.value.enabled
+ prefix = lifecycle_rule.value.prefix
+ tags = lifecycle_rule.value.tags
+ abort_incomplete_multipart_upload_days = lifecycle_rule.value.abort_incomplete_multipart_upload_days
- noncurrent_version_expiration {
- days = var.noncurrent_version_expiration_days
- }
+ noncurrent_version_expiration {
+ days = lifecycle_rule.value.noncurrent_version_expiration_days
+ }
- dynamic "noncurrent_version_transition" {
- for_each = var.enable_glacier_transition ? [1] : []
+ dynamic "noncurrent_version_transition" {
+ for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : []
- content {
- days = var.noncurrent_version_glacier_transition_days
- storage_class = "GLACIER"
+ content {
+ days = lifecycle_rule.value.noncurrent_version_glacier_transition_days
+ storage_class = "GLACIER"
+ }
}
- }
- dynamic "noncurrent_version_transition" {
- for_each = var.enable_deeparchive_transition ? [1] : []
+ dynamic "noncurrent_version_transition" {
+ for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : []
- content {
- days = var.noncurrent_version_deeparchive_transition_days
- storage_class = "DEEP_ARCHIVE"
+ content {
+ days = lifecycle_rule.value.noncurrent_version_deeparchive_transition_days
+ storage_class = "DEEP_ARCHIVE"
+ }
}
- }
- dynamic "transition" {
- for_each = var.enable_glacier_transition ? [1] : []
+ dynamic "transition" {
+ for_each = lifecycle_rule.value.enable_glacier_transition ? [1] : []
- content {
- days = var.glacier_transition_days
- storage_class = "GLACIER"
+ content {
+ days = lifecycle_rule.value.glacier_transition_days
+ storage_class = "GLACIER"
+ }
}
- }
- dynamic "transition" {
- for_each = var.enable_deeparchive_transition ? [1] : []
+ dynamic "transition" {
+ for_each = lifecycle_rule.value.enable_deeparchive_transition ? [1] : []
- content {
- days = var.deeparchive_transition_days
- storage_class = "DEEP_ARCHIVE"
+ content {
+ days = lifecycle_rule.value.deeparchive_transition_days
+ storage_class = "DEEP_ARCHIVE"
+ }
}
- }
- dynamic "transition" {
- for_each = var.enable_standard_ia_transition ? [1] : []
+ dynamic "transition" {
+ for_each = lifecycle_rule.value.enable_standard_ia_transition ? [1] : []
- content {
- days = var.standard_transition_days
- storage_class = "STANDARD_IA"
+ content {
+ days = lifecycle_rule.value.standard_transition_days
+ storage_class = "STANDARD_IA"
+ }
}
- }
- dynamic "expiration" {
- for_each = var.enable_current_object_expiration ? [1] : []
+ dynamic "expiration" {
+ for_each = lifecycle_rule.value.enable_current_object_expiration ? [1] : []
- content {
- days = var.expiration_days
+ content {
+ days = lifecycle_rule.value.expiration_days
+ }
}
}
}
diff --git a/test/src/examples_complete_test.go b/test/src/examples_complete_test.go
index da3c9ede..97be8c05 100644
--- a/test/src/examples_complete_test.go
+++ b/test/src/examples_complete_test.go
@@ -129,13 +129,55 @@ func TestExamplesCompleteWithObjectLock(t *testing.T) {
userName := terraform.Output(t, terraformOptions, "user_name")
expectedUserName := "eg-test-s3-object-lock-test-" + attributes[0]
+
// Verify we're getting back the outputs we expect
assert.Equal(t, expectedUserName, userName)
// Run `terraform output` to get the value of an output variable
s3BucketId := terraform.Output(t, terraformOptions, "bucket_id")
-
expectedS3BucketId := "eg-test-s3-object-lock-test-" + attributes[0]
// Verify we're getting back the outputs we expect
assert.Equal(t, expectedS3BucketId, s3BucketId)
}
+
+func TestExamplesCompleteWithLifecycleRules(t *testing.T) {
+ rand.Seed(time.Now().UnixNano())
+
+ attributes := []string{strconv.Itoa(rand.Intn(100000))}
+ rootFolder := "../../"
+ terraformFolderRelativeToRoot := "examples/complete"
+ varFiles := []string{"lifecycle.us-west-1.tfvars"}
+
+ tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
+
+ terraformOptions := &terraform.Options{
+ // The path to where our Terraform code is located
+ TerraformDir: tempTestFolder,
+ Upgrade: true,
+ // Variables to pass to our Terraform code using -var-file options
+ VarFiles: varFiles,
+ Vars: map[string]interface{}{
+ "attributes": attributes,
+ },
+ }
+
+ // At the end of the test, run `terraform destroy` to clean up any resources that were created
+ defer terraform.Destroy(t, terraformOptions)
+
+ // This will run `terraform init` and `terraform apply` and fail the test if there are any errors
+ terraform.InitAndApply(t, terraformOptions)
+
+ // Run `terraform output` to get the value of an output variable
+ userName := terraform.Output(t, terraformOptions, "user_name")
+
+ expectedUserName := "eg-test-s3-lifecycle-test-" + attributes[0]
+ // Verify we're getting back the outputs we expect
+ assert.Equal(t, expectedUserName, userName)
+
+ // Run `terraform output` to get the value of an output variable
+ s3BucketId := terraform.Output(t, terraformOptions, "bucket_id")
+
+ expectedS3BucketId := "eg-test-s3-lifecycle-test-" + attributes[0]
+ // Verify we're getting back the outputs we expect
+ assert.Equal(t, expectedS3BucketId, s3BucketId)
+}
diff --git a/variables.tf b/variables.tf
index 9ff8ed53..021070a0 100644
--- a/variables.tf
+++ b/variables.tf
@@ -79,34 +79,48 @@ variable "allow_ssl_requests_only" {
description = "Set to `true` to require requests to use Secure Socket Layer (HTTPS/SSL). This will explicitly deny access to HTTP requests"
}
-variable "lifecycle_rule_enabled" {
- type = bool
- default = false
- description = "Enable or disable lifecycle rule"
-}
-
-variable "prefix" {
- type = string
- default = ""
- description = "Prefix identifying one or more objects to which the rule applies"
-}
+variable "lifecycle_rules" {
+ type = list(object({
+ prefix = string
+ enabled = bool
+ tags = map(string)
+
+ enable_glacier_transition = bool
+ enable_deeparchive_transition = bool
+ enable_standard_ia_transition = bool
+ enable_current_object_expiration = bool
+
+ abort_incomplete_multipart_upload_days = number
+ noncurrent_version_glacier_transition_days = number
+ noncurrent_version_deeparchive_transition_days = number
+ noncurrent_version_expiration_days = number
+
+ standard_transition_days = number
+ glacier_transition_days = number
+ deeparchive_transition_days = number
+ expiration_days = number
+ }))
+ default = [{
+ enabled = false
+ prefix = ""
+ tags = {}
-variable "noncurrent_version_glacier_transition_days" {
- type = number
- default = 30
- description = "Number of days to persist in the standard storage tier before moving to the glacier infrequent access tier"
-}
+ enable_glacier_transition = true
+ enable_deeparchive_transition = false
+ enable_standard_ia_transition = false
+ enable_current_object_expiration = true
-variable "noncurrent_version_deeparchive_transition_days" {
- type = number
- default = 60
- description = "Number of days to persist in the standard storage tier before moving to the glacier deeparchive access tier"
-}
+ abort_incomplete_multipart_upload_days = 90
+ noncurrent_version_glacier_transition_days = 30
+ noncurrent_version_deeparchive_transition_days = 60
+ noncurrent_version_expiration_days = 90
-variable "noncurrent_version_expiration_days" {
- type = number
- default = 90
- description = "Specifies when noncurrent object versions expire"
+ standard_transition_days = 30
+ glacier_transition_days = 60
+ deeparchive_transition_days = 90
+ expiration_days = 90
+ }]
+ description = "A list of lifecycle rules"
}
variable "cors_rule_inputs" {
@@ -122,65 +136,12 @@ variable "cors_rule_inputs" {
description = "Specifies the allowed headers, methods, origins and exposed headers when using CORS on this bucket"
}
-variable "standard_transition_days" {
- type = number
- default = 30
- description = "Number of days to persist in the standard storage tier before moving to the infrequent access tier"
-}
-
-variable "glacier_transition_days" {
- type = number
- default = 60
- description = "Number of days after which to move the data to the glacier storage tier"
-}
-
-variable "deeparchive_transition_days" {
- type = number
- default = 90
- description = "Number of days after which to move the data to the glacier deep archive storage tier"
-}
-
-variable "enable_glacier_transition" {
- type = bool
- default = true
- description = "Enables the transition to AWS Glacier which can cause unnecessary costs for huge amount of small files"
-}
-
-variable "enable_deeparchive_transition" {
- type = bool
- default = false
- description = "Enables the transition to AWS Glacier Deep Archive which can cause unnecessary costs for huge amount of small files"
-}
-variable "enable_standard_ia_transition" {
- type = bool
- default = false
- description = "Enables the transition to STANDARD_IA"
-}
-
-variable "enable_current_object_expiration" {
- type = bool
- default = true
- description = "Enables the expiration of current objects"
-}
-
-variable "expiration_days" {
- type = number
- default = 90
- description = "Number of days after which to expunge the objects"
-}
-
variable "abort_incomplete_multipart_upload_days" {
type = number
default = 5
description = "Maximum time (in days) that you want to allow multipart uploads to remain in progress"
}
-variable "lifecycle_tags" {
- type = map(string)
- description = "Tags filter. Used to manage object lifecycle events"
- default = {}
-}
-
variable "block_public_acls" {
type = bool
default = true