diff --git a/.secrets.baseline b/.secrets.baseline index aa6a4952..3e57585c 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": "go.sum|^.secrets.baseline$", "lines": null }, - "generated_at": "2025-11-18T06:26:49Z", + "generated_at": "2025-11-28T08:21:42Z", "plugins_used": [ { "name": "AWSKeyDetector" @@ -82,7 +82,7 @@ "hashed_secret": "ff9ee043d85595eb255c05dfe32ece02a53efbb2", "is_secret": false, "is_verified": false, - "line_number": 40, + "line_number": 41, "type": "Secret Keyword", "verified_result": null }, @@ -90,7 +90,7 @@ "hashed_secret": "a7c93faaa770c377154ea9d4d0d17a9056dbfa95", "is_secret": false, "is_verified": false, - "line_number": 193, + "line_number": 194, "type": "Secret Keyword", "verified_result": null } diff --git a/README.md b/README.md index 70addc10..0d9c356d 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ You can configure the following aspects of your instances: * [Submodules](./modules) * [buckets](./modules/buckets) * [fscloud](./modules/fscloud) + * [lifecycle_rules](./modules/lifecycle_rules) * [Examples](./examples) *
* diff --git a/common-dev-assets b/common-dev-assets index 6a351e62..5f830744 160000 --- a/common-dev-assets +++ b/common-dev-assets @@ -1 +1 @@ -Subproject commit 6a351e62a04ea8bf074d4933c86cd28671154d3b +Subproject commit 5f830744586a45ffc2b2b75550ee23f4f343677f diff --git a/examples/basic/main.tf b/examples/basic/main.tf index 1d619ea7..5ec533d2 100644 --- a/examples/basic/main.tf +++ b/examples/basic/main.tf @@ -15,16 +15,18 @@ module "resource_group" { ############################################################################## module "cos" { - source = "../../" - resource_group_id = module.resource_group.resource_group_id - region = var.region - cos_instance_name = "${var.prefix}-cos" - cos_tags = var.resource_tags - bucket_name = "${var.prefix}-bucket" - retention_enabled = false # disable retention for test environments - enable for stage/prod - kms_encryption_enabled = false - cos_plan = "cos-one-rate-plan" - bucket_storage_class = "onerate_active" + source = "../../" + resource_group_id = module.resource_group.resource_group_id + region = var.region + cos_instance_name = "${var.prefix}-cos" + cos_tags = var.resource_tags + bucket_name = "${var.prefix}-bucket" + retention_enabled = false # disable retention for test environments - enable for stage/prod + kms_encryption_enabled = false + cos_plan = "cos-one-rate-plan" + bucket_storage_class = "onerate_active" + object_versioning_enabled = true + access_tags = var.access_tags } ############################################################################## @@ -43,3 +45,52 @@ module "buckets" { } ] } + +############################################################################## +# Create Multiple Lifecycle Rules using lifecycle_rules submodule +############################################################################## + +module "advance_lifecycle_rules" { + source = "../../modules/lifecycle_rules" + cos_region = var.region + bucket_crn = module.cos.bucket_crn + expiry_rules = [ + { + rule_id = "expiry-info-7d" + days = 7 + prefix = "info-" + }, + { + rule_id = "expiry-error-30d" + days = 30 + prefix = "error-" + } + ] + + noncurrent_expiry_rules = [ + { + rule_id = "ncv-expire-45d" + noncurrent_days = 45 + prefix = "data/" + }, + { + rule_id = "ncv-expire-90d" + noncurrent_days = 90 + prefix = "archive/" + } + ] + + abort_multipart_rules = [ + { + rule_id = "abort-stale-7d" + days_after_initiation = 7 + prefix = "" + }, + { + rule_id = "abort-temp-3d" + days_after_initiation = 3 + prefix = "tmp/" + } + ] + +} diff --git a/examples/basic/variables.tf b/examples/basic/variables.tf index 6a37d966..f4322b1d 100644 --- a/examples/basic/variables.tf +++ b/examples/basic/variables.tf @@ -25,3 +25,9 @@ variable "resource_group" { description = "An existing resource group name to use for this example, if unset a new resource group will be created" default = null } + +variable "access_tags" { + type = list(string) + description = "Optional list of access tags to be added to the created resources" + default = [] +} diff --git a/modules/lifecycle_rules/README.md b/modules/lifecycle_rules/README.md new file mode 100644 index 00000000..ccedd799 --- /dev/null +++ b/modules/lifecycle_rules/README.md @@ -0,0 +1,96 @@ +# Cloud Object Storage lifecycle_rules module + +You can use this submodule to configure multiple lifecycle rules to existing IBM [Cloud Object Storage](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-getting-started-cloud-object-storage) buckets. + +You can configure the following multiple rules to your buckets: +- [expiration](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-expiry) +- [noncurrent version expiration](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-expiry#noncurrentversionexpiration) +- [abort incomplete multipart](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-lifecycle-cleanup-mpu) + +### Usage +```hcl +module "advance_lifecycle_rules" { + source = "terraform-ibm-modules/cos/ibm//modules/lifecycle_rules" + cos_region = "region of the existing bucket" + bucket_crn = "crn:v1:bluemix:public:cloud-object-storage:global:a/xxXXxxXXxXxXXXXxxXxxxXXXXxXXXXX:bucket:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx" # existing bucket crn + object_versioning_enabled = false # False by default , must be set to true for noncurrent version expiration lifecycle rule + expiry_rules = [ + { + rule_id = "expiry-info-7d" + days = 7 + prefix = "info-" + }, + { + rule_id = "expiry-error-30d" + days = 30 + prefix = "error-" + } + ] + + noncurrent_expiry_rules = [ + { + rule_id = "ncv-expire-45d" + noncurrent_days = 45 + prefix = "data/" + }, + { + rule_id = "ncv-expire-90d" + noncurrent_days = 90 + prefix = "archive/" + } + ] + + abort_multipart_rules = [ + { + rule_id = "abort-stale-7d" + days_after_initiation = 7 + prefix = "" + }, + { + rule_id = "abort-temp-3d" + days_after_initiation = 3 + prefix = "tmp/" + } + ] + +} +``` + + + +### Requirements + +| Name | Version | +|------|---------| +| [terraform](#requirement\_terraform) | >= 1.9.0 | +| [ibm](#requirement\_ibm) | >= 1.79.2, < 2.0.0 | + +### Modules + +No modules. + +### Resources + +| Name | Type | +|------|------| +| [ibm_cos_bucket_lifecycle_configuration.advance_bucket_lifecycle](https://registry.terraform.io/providers/ibm-cloud/ibm/latest/docs/resources/cos_bucket_lifecycle_configuration) | resource | + +### Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| [abort\_multipart\_rules](#input\_abort\_multipart\_rules) | List of abort incomplete multipart upload rules |list(object({
rule_id = optional(string)
status = optional(string, "enable")
days_after_initiation = number
prefix = optional(string, "")
})) | `[]` | no |
+| [bucket\_crn](#input\_bucket\_crn) | The CRN of an existing Cloud Object Storage bucket. | `string` | n/a | yes |
+| [cos\_region](#input\_cos\_region) | The region of existing Cloud Object Storage bucket. | `string` | n/a | yes |
+| [expiry\_rules](#input\_expiry\_rules) | List of expiry rules | list(object({
rule_id = optional(string)
status = optional(string, "enable")
days = number
prefix = optional(string, "")
})) | `[]` | no |
+| [management\_endpoint\_type\_for\_bucket](#input\_management\_endpoint\_type\_for\_bucket) | The type of endpoint for the IBM terraform provider to manage the bucket. Possible values are `public`, `private`, or `direct`. | `string` | `"public"` | no |
+| [noncurrent\_expiry\_rules](#input\_noncurrent\_expiry\_rules) | List of noncurrent version expiration rules , note : this lifecycle rule requires object versioning make sure object versioning is enabled on the bucket | list(object({
rule_id = optional(string)
status = optional(string, "enable")
noncurrent_days = number
prefix = optional(string, "")
})) | `[]` | no |
+
+### Outputs
+
+| Name | Description |
+|------|-------------|
+| [bucket\_crn](#output\_bucket\_crn) | Bucket CRN |
+| [bucket\_id](#output\_bucket\_id) | Bucket ID |
+| [bucket\_location](#output\_bucket\_location) | Bucket location |
+
diff --git a/modules/lifecycle_rules/main.tf b/modules/lifecycle_rules/main.tf
new file mode 100644
index 00000000..33dfc565
--- /dev/null
+++ b/modules/lifecycle_rules/main.tf
@@ -0,0 +1,74 @@
+locals {
+ # Assign deterministic rule IDs if not provided
+ expiry_rules = [
+ for idx, r in var.expiry_rules : merge(r, {
+ rule_id = coalesce(try(r.rule_id, null), "expiry-rule-${idx}")
+ })
+ ]
+
+ noncurrent_expiry_rules = [
+ for idx, r in var.noncurrent_expiry_rules : merge(r, {
+ rule_id = coalesce(try(r.rule_id, null), "noncurrent-expiry-rule-${idx}")
+ })
+ ]
+
+ abort_multipart_rules = [
+ for idx, r in var.abort_multipart_rules : merge(r, {
+ rule_id = coalesce(try(r.rule_id, null), "abort-multipart-rule-${idx}")
+ })
+ ]
+}
+
+
+resource "ibm_cos_bucket_lifecycle_configuration" "advance_bucket_lifecycle" {
+ bucket_crn = var.bucket_crn
+ bucket_location = var.cos_region
+ endpoint_type = var.management_endpoint_type_for_bucket
+
+ # Expiration rules
+ dynamic "lifecycle_rule" {
+ for_each = local.expiry_rules
+ content {
+ expiration {
+ days = lifecycle_rule.value.days
+ }
+ filter {
+ prefix = lifecycle_rule.value.prefix
+ }
+ rule_id = lifecycle_rule.value.rule_id
+ status = lifecycle_rule.value.status
+ }
+ }
+
+
+ # Noncurrent version expiration rules
+ dynamic "lifecycle_rule" {
+ for_each = local.noncurrent_expiry_rules
+ content {
+ noncurrent_version_expiration {
+ noncurrent_days = lifecycle_rule.value.noncurrent_days
+ }
+ filter {
+ prefix = lifecycle_rule.value.prefix
+ }
+ rule_id = lifecycle_rule.value.rule_id
+ status = lifecycle_rule.value.status
+ }
+ }
+
+ # Abort multipart rules
+ dynamic "lifecycle_rule" {
+ for_each = local.abort_multipart_rules
+ content {
+ abort_incomplete_multipart_upload {
+ days_after_initiation = lifecycle_rule.value.days_after_initiation
+ }
+ filter {
+ prefix = lifecycle_rule.value.prefix
+ }
+ rule_id = lifecycle_rule.value.rule_id
+ status = lifecycle_rule.value.status
+ }
+ }
+
+}
diff --git a/modules/lifecycle_rules/outputs.tf b/modules/lifecycle_rules/outputs.tf
new file mode 100644
index 00000000..893e1837
--- /dev/null
+++ b/modules/lifecycle_rules/outputs.tf
@@ -0,0 +1,14 @@
+output "bucket_crn" {
+ value = ibm_cos_bucket_lifecycle_configuration.advance_bucket_lifecycle.bucket_crn
+ description = "Bucket CRN"
+}
+
+output "bucket_location" {
+ value = ibm_cos_bucket_lifecycle_configuration.advance_bucket_lifecycle.bucket_location
+ description = "Bucket location"
+}
+
+output "bucket_id" {
+ value = ibm_cos_bucket_lifecycle_configuration.advance_bucket_lifecycle.id
+ description = "Bucket ID"
+}
diff --git a/modules/lifecycle_rules/variables.tf b/modules/lifecycle_rules/variables.tf
new file mode 100644
index 00000000..3c3be0c3
--- /dev/null
+++ b/modules/lifecycle_rules/variables.tf
@@ -0,0 +1,67 @@
+variable "bucket_crn" {
+ description = "The CRN of an existing Cloud Object Storage bucket."
+ type = string
+}
+
+variable "cos_region" {
+ description = "The region of existing Cloud Object Storage bucket."
+ type = string
+}
+
+variable "management_endpoint_type_for_bucket" {
+ description = "The type of endpoint for the IBM terraform provider to manage the bucket. Possible values are `public`, `private`, or `direct`."
+ type = string
+ default = "public"
+ validation {
+ condition = contains(["public", "private", "direct"], var.management_endpoint_type_for_bucket)
+ error_message = "The value isn't valid. Possible values are `public`, `private`, or `direct`."
+ }
+}
+
+variable "expiry_rules" {
+ description = "List of expiry rules"
+ type = list(object({
+ rule_id = optional(string)
+ status = optional(string, "enable")
+ days = number
+ prefix = optional(string, "")
+ }))
+ default = []
+
+ validation {
+ condition = alltrue([for r in var.expiry_rules : r.days >= 1])
+ error_message = "Expiry days must be >= 1."
+ }
+
+}
+variable "noncurrent_expiry_rules" {
+ description = "List of noncurrent version expiration rules , note : this lifecycle rule requires object versioning make sure object versioning is enabled on the bucket"
+ type = list(object({
+ rule_id = optional(string)
+ status = optional(string, "enable")
+ noncurrent_days = number
+ prefix = optional(string, "")
+ }))
+ default = []
+
+ validation {
+ condition = alltrue([for r in var.noncurrent_expiry_rules : try(r.noncurrent_days, 0) >= 1])
+ error_message = "Each noncurrent version expiration rule must have noncurrent_days >= 1."
+ }
+}
+
+variable "abort_multipart_rules" {
+ description = "List of abort incomplete multipart upload rules"
+ type = list(object({
+ rule_id = optional(string)
+ status = optional(string, "enable")
+ days_after_initiation = number
+ prefix = optional(string, "")
+ }))
+ default = []
+
+ validation {
+ condition = alltrue([for r in var.abort_multipart_rules : try(r.days_after_initiation, 0) >= 1])
+ error_message = "Each abort multipart rule must have days_after_initiation >= 1."
+ }
+}
diff --git a/modules/lifecycle_rules/version.tf b/modules/lifecycle_rules/version.tf
new file mode 100644
index 00000000..ce16e007
--- /dev/null
+++ b/modules/lifecycle_rules/version.tf
@@ -0,0 +1,12 @@
+terraform {
+ required_version = ">= 1.9.0"
+
+ # Use a flexible range in modules that future proofs the module's usage with upcoming minor and patch versions
+ required_providers {
+ # tflint-ignore: terraform_unused_required_providers
+ ibm = {
+ source = "ibm-cloud/ibm"
+ version = ">= 1.79.2, < 2.0.0"
+ }
+ }
+}