diff --git a/examples/ibm-cos-bucket/README.md b/examples/ibm-cos-bucket/README.md index 91d220a059..694b279549 100644 --- a/examples/ibm-cos-bucket/README.md +++ b/examples/ibm-cos-bucket/README.md @@ -510,6 +510,36 @@ resource ibm_cos_bucket_website_configuration "website_configuration" { } } ``` +## ibm_cos_bucket_lifecycle_configuration + +Provides an independent resource to manage the lifecycle configuration for a bucket.For more information please refer to [`ibm_cos_bucket_lifecycle_configuration`](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/ibm_cos_bucket_lifecycle_configuration) + +## Example usage + +```terraform +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 1 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } +} +``` ## Requirements @@ -582,4 +612,15 @@ resource ibm_cos_bucket_website_configuration "website_configuration" { | http_redirect_code | HTTP redirect code to use on the response. | `string` | No | replace_key_with | Specific object key to use in the redirect request. | `string` | No | replace_key_prefix_with | Object key prefix to use in the redirect request. | `string` | No +| days | Days after which the lifecycle rule expiration will be applied on the object. | `int` | No +| date | Date after which the lifecycle rule expiration will be applied on the object. | `int` | No +| expire_object_delete_marker | Indicates whether ibm will remove a delete marker with no noncurrent versions. | `bool` | No +| days | Days after which the lifecycle rule transition will be applied on the object. | `int` | No +| date | Date after which the lifecycle rule transition will be applied on the object. | `int` | No +| storage_class | Class of storage used to store the object. | `string` | No +| noncurrent_days | Number of days an object is noncurrent before lifecycle action is performed. | `int` | No +| days_after_initiatiob | Number of days after which incomplete multipart uploads are aborted. | `int` | No +| id | Unique identifier for lifecycle rule. | `int` | Yes +| status | Whether the rule is currently being applied. | `int` | Yes + {: caption="inputs"} diff --git a/examples/ibm-cos-bucket/main.tf b/examples/ibm-cos-bucket/main.tf index 8e83a6f669..0e97cbe65e 100644 --- a/examples/ibm-cos-bucket/main.tf +++ b/examples/ibm-cos-bucket/main.tf @@ -297,7 +297,7 @@ resource "ibm_kms_key" "key" { force_delete = true } -resource "ibm_cos_bucket" "hpcs-enabled" { +resource "ibm_cos_bucket" "hpcs-enable" { depends_on = [ibm_iam_authorization_policy.policy2] bucket_name = var.bucket_name resource_instance_id = ibm_resource_instance.cos_instance.id @@ -307,7 +307,7 @@ resource "ibm_cos_bucket" "hpcs-enabled" { } //HPCS - UKO plan -resource "ibm_cos_bucket" "hpcs-uko-enabled" { +resource "ibm_cos_bucket" "hpcs-uko-enable" { depends_on = [ibm_iam_authorization_policy.policy2] bucket_name = var.bucket_name resource_instance_id = ibm_resource_instance.cos_instance.id @@ -386,7 +386,7 @@ resource ibm_cos_bucket_object_lock_configuration "objectlock" { bucket_crn = ibm_cos_bucket.bucket.crn bucket_location = var.regional_loc object_lock_configuration{ - object_lock_enabled = "Enabled" + object_lock_enable = "Enabled" object_lock_rule{ default_retention{ mode = "COMPLIANCE" @@ -500,3 +500,146 @@ resource ibm_cos_bucket_website_configuration "website_configuration" { EOF } } + + +#COS Lifecycle Configuration + +# Adding lifecycle configuration with expiration and prefix filter. + +resource "ibm_cos_bucket" "cos_bucket_lifecycle_expiration" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 1 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } +} + + + +# Adding lifecycle configuration with transition. + +resource "ibm_cos_bucket" "cos_bucket_transition" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle_transition" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + transition{ + days = 1 + storage_class = "GLACIER" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } +} + + +# Adding lifecycle configuration with abort incomplete multipart upload. + +resource "ibm_cos_bucket" "cos_bucket_abort_incomplete" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle_abort_incomplete" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + abort_incomplete_multipart_upload{ + days_after_initiation = 1 + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } +} + + +# Adding lifecycle configuration with non current version expiration. + +resource "ibm_cos_bucket" "cos_bucket_lifecycle_version_expiration" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle_new" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + noncurrent_version_expiration{ + noncurrent_days = "1" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } +} + +# Adding lifecycle configuration with multiple rules + +resource "ibm_cos_bucket" "cos_bucket_lifecycle" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class +} + +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle_config" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 1 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + } + lifecycle_rule { + expiration{ + days = 2 + } + filter { + prefix = "bar" + } + rule_id = "id2" + status = "enable" + } +} + diff --git a/ibm/flex/structures.go b/ibm/flex/structures.go index 147a0d6bba..536deb107e 100644 --- a/ibm/flex/structures.go +++ b/ibm/flex/structures.go @@ -930,6 +930,111 @@ func ReplicationRuleGet(in *s3.ReplicationConfiguration) []map[string]interface{ return rules } +func flattenLifecycleExpiration(expiration *s3.LifecycleExpiration) []interface{} { + if expiration == nil { + return []interface{}{} + } + m := make(map[string]interface{}) + if expiration.Date != nil { + m["date"] = expiration.Date.Format(time.RFC3339) + } + if expiration.Days != nil { + m["days"] = int(aws.Int64Value(expiration.Days)) + } + if expiration.ExpiredObjectDeleteMarker != nil { + m["expired_object_delete_marker"] = aws.Bool(*expiration.ExpiredObjectDeleteMarker) + } + return []interface{}{m} +} + +func flattenNoncurrentVersionExpiration(expiration *s3.NoncurrentVersionExpiration) []interface{} { + if expiration == nil { + return []interface{}{} + } + m := make(map[string]interface{}) + if expiration.NoncurrentDays != nil { + m["noncurrent_days"] = int(aws.Int64Value(expiration.NoncurrentDays)) + } + return []interface{}{m} +} +func flattenTransitions(transitions []*s3.Transition) []interface{} { + if len(transitions) == 0 { + return []interface{}{} + } + var results []interface{} + for _, transition := range transitions { + m := make(map[string]interface{}) + if transition.StorageClass != nil { + m["storage_class"] = transition.StorageClass + } + if transition.Date != nil { + m["date"] = transition.Date.Format(time.RFC3339) + } + if transition.Days != nil { + m["days"] = int(aws.Int64Value(transition.Days)) + } + results = append(results, m) + } + return results +} + +func flattenLifecycleRuleFilter(filter *s3.LifecycleRuleFilter) []interface{} { + if filter == nil { + return []interface{}{} + } + m := make(map[string]interface{}) + if filter.Prefix != nil { + m["prefix"] = aws.String(*filter.Prefix) + } + return []interface{}{m} +} + +func flattenAbortIncompleteMultipartUpload(abortIncompleteMultipartUploadInput *s3.AbortIncompleteMultipartUpload) []interface{} { + if abortIncompleteMultipartUploadInput == nil { + return []interface{}{} + } + abortIncompleteMultipartUploadMap := make(map[string]interface{}) + if abortIncompleteMultipartUploadInput.DaysAfterInitiation != nil { + abortIncompleteMultipartUploadMap["days_after_initiation"] = int(aws.Int64Value(abortIncompleteMultipartUploadInput.DaysAfterInitiation)) + } + return []interface{}{abortIncompleteMultipartUploadMap} +} + +func LifecylceRuleGet(lifecycleRuleInput []*s3.LifecycleRule) []map[string]interface{} { + rules := make([]map[string]interface{}, 0, len(lifecycleRuleInput)) + if lifecycleRuleInput != nil { + for _, lifecyclerule := range lifecycleRuleInput { + lifecycleRuleConfig := make(map[string]interface{}) + if lifecyclerule.Status != nil { + if *lifecyclerule.Status == "Enabled" { + lifecycleRuleConfig["status"] = "enable" + } else { + lifecycleRuleConfig["status"] = "disable" + } + } + if lifecyclerule.ID != nil { + lifecycleRuleConfig["rule_id"] = *lifecyclerule.ID + } + if lifecyclerule.Expiration != nil { + lifecycleRuleConfig["expiration"] = flattenLifecycleExpiration(lifecyclerule.Expiration) + } + if lifecyclerule.Transitions != nil { + lifecycleRuleConfig["transition"] = flattenTransitions(lifecyclerule.Transitions) + } + if lifecyclerule.AbortIncompleteMultipartUpload != nil { + lifecycleRuleConfig["abort_incomplete_multipart_upload"] = flattenAbortIncompleteMultipartUpload(lifecyclerule.AbortIncompleteMultipartUpload) + } + if lifecyclerule.NoncurrentVersionExpiration != nil { + lifecycleRuleConfig["noncurrent_version_expiration"] = flattenNoncurrentVersionExpiration(lifecyclerule.NoncurrentVersionExpiration) + } + if lifecyclerule.Filter != nil { + lifecycleRuleConfig["filter"] = flattenLifecycleRuleFilter(lifecyclerule.Filter) + } + rules = append(rules, lifecycleRuleConfig) + } + } + return rules +} func ObjectLockConfigurationGet(in *s3.ObjectLockConfiguration) []map[string]interface{} { configuration := make([]map[string]interface{}, 0, 1) if in != nil { diff --git a/ibm/provider/provider.go b/ibm/provider/provider.go index 2d94cf08f0..a8da05c7ba 100644 --- a/ibm/provider/provider.go +++ b/ibm/provider/provider.go @@ -1095,6 +1095,7 @@ func Provider() *schema.Provider { "ibm_cos_bucket_object": cos.ResourceIBMCOSBucketObject(), "ibm_cos_bucket_object_lock_configuration": cos.ResourceIBMCOSBucketObjectlock(), "ibm_cos_bucket_website_configuration": cos.ResourceIBMCOSBucketWebsiteConfiguration(), + "ibm_cos_bucket_lifecycle_configuration": cos.ResourceIBMCOSBucketLifecycleConfiguration(), "ibm_dns_domain": classicinfrastructure.ResourceIBMDNSDomain(), "ibm_dns_domain_registration_nameservers": classicinfrastructure.ResourceIBMDNSDomainRegistrationNameservers(), "ibm_dns_secondary": classicinfrastructure.ResourceIBMDNSSecondary(), diff --git a/ibm/service/cos/data_source_ibm_cos_bucket.go b/ibm/service/cos/data_source_ibm_cos_bucket.go index 1e8316b38e..653fe61390 100644 --- a/ibm/service/cos/data_source_ibm_cos_bucket.go +++ b/ibm/service/cos/data_source_ibm_cos_bucket.go @@ -556,6 +556,99 @@ func DataSourceIBMCosBucket() *schema.Resource { Type: schema.TypeString, Computed: true, }, + "lifecycle_rule": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "abort_incomplete_multipart_upload": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "days_after_initiation": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "expiration": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "date": { + Type: schema.TypeString, + Computed: true, + }, + "days": { + Type: schema.TypeInt, + Computed: true, + }, + "expired_object_delete_marker": { + Type: schema.TypeBool, + Computed: true, // API returns false; conflicts with date and days + }, + }, + }, + }, + "filter": { + Type: schema.TypeList, + Computed: true, + // IBM has filter a required parameter + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + "rule_id": { + Type: schema.TypeString, + Computed: true, + }, + "noncurrent_version_expiration": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "noncurrent_days": { + Type: schema.TypeInt, + Computed: true, + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Computed: true, + }, + "transition": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "date": { + Type: schema.TypeString, + Computed: true, + }, + "days": { + Type: schema.TypeInt, + Computed: true, + }, + "storag_class": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + }, + }, }, } } @@ -797,6 +890,24 @@ func dataSourceIBMCosBucketRead(d *schema.ResourceData, meta interface{}) error } } + //lifecycle configuration new resource read + getLifecycleConfigurationInput := &s3.GetBucketLifecycleConfigurationInput{ + Bucket: aws.String(bucketName), + } + var outputLifecycleConfig *s3.GetBucketLifecycleConfigurationOutput + outputLifecycleConfig, err = s3Client.GetBucketLifecycleConfiguration(getLifecycleConfigurationInput) + var outputLifecycleConfigptr *s3.LifecycleConfiguration + outputLifecycleConfigptr = (*s3.LifecycleConfiguration)(outputLifecycleConfig) + if (err != nil && !strings.Contains(err.Error(), "NoSuchLifecycleConfiguration: The lifecycle configuration does not exist")) && (err != nil && bucketPtr != nil && bucketPtr.Firewall != nil && !strings.Contains(err.Error(), "AccessDenied: Access Denied")) { + return err + } + if outputLifecycleConfigptr.Rules != nil { + lifecycleConfiguration := flex.LifecylceRuleGet(outputLifecycleConfigptr.Rules) + if len(lifecycleConfiguration) > 0 { + d.Set("lifecycle_rule", lifecycleConfiguration) + } + } + // Read the retention policy retentionInput := &s3.GetBucketProtectionConfigurationInput{ Bucket: aws.String(bucketName), diff --git a/ibm/service/cos/resource_ibm_cos_bucket.go b/ibm/service/cos/resource_ibm_cos_bucket.go index 9637374383..912f2b51dd 100644 --- a/ibm/service/cos/resource_ibm_cos_bucket.go +++ b/ibm/service/cos/resource_ibm_cos_bucket.go @@ -244,7 +244,8 @@ func ResourceIBMCOSBucket() *schema.Resource { "abort_incomplete_multipart_upload_days": { Type: schema.TypeList, Optional: true, - MaxItems: 1, + MaxItems: 1000, + Deprecated: "Use the ibm_cos_bucket_lifecycle_configuration resource instead", Description: "Enable abort incomplete multipart upload to COS Bucket after a defined period of time", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -278,6 +279,7 @@ func ResourceIBMCOSBucket() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1, + Deprecated: "Use the ibm_cos_bucket_lifecycle_configuration resource instead", Description: "Enable configuration archive_rule (glacier/accelerated) to COS Bucket after a defined period of time", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -312,6 +314,7 @@ func ResourceIBMCOSBucket() *schema.Resource { Type: schema.TypeList, Optional: true, MaxItems: 1000, + Deprecated: "Use the ibm_cos_bucket_lifecycle_configuration resource instead", Description: "Enable configuration expire_rule to COS Bucket after a defined period of time", Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -1085,6 +1088,7 @@ func resourceIBMCOSBucketUpdate(d *schema.ResourceData, meta interface{}) error func resourceIBMCOSBucketRead(d *schema.ResourceData, meta interface{}) error { var s3Conf *aws.Config var keyProtectFlag bool + var archiveFlag, expireFlag, abortFlag, ncFlag bool rsConClient, err := meta.(conns.ClientSession).BluemixSession() if err != nil { return err @@ -1101,6 +1105,18 @@ func resourceIBMCOSBucketRead(d *schema.ResourceData, meta interface{}) error { if _, ok := d.GetOk("key_protect"); ok { keyProtectFlag = true } + if _, ok := d.GetOk("expire_rule"); ok { + expireFlag = true + } + if _, ok := d.GetOk("archive_rule"); ok { + archiveFlag = true + } + if _, ok := d.GetOk("noncurrent_version_expiration"); ok { + archiveFlag = true + } + if _, ok := d.GetOk("abort_incomplete_multipart_upload_days"); ok { + abortFlag = true + } //split satellite resource instance id to get the 1st value if apiType == "sl" { @@ -1287,23 +1303,27 @@ func resourceIBMCOSBucketRead(d *schema.ResourceData, meta interface{}) error { if (err != nil && !strings.Contains(err.Error(), "NoSuchLifecycleConfiguration: The lifecycle configuration does not exist")) && (err != nil && bucketPtr != nil && bucketPtr.Firewall != nil && !strings.Contains(err.Error(), "AccessDenied: Access Denied")) { return err } - if lifecycleptr != nil { + + if lifecycleptr.Rules != nil { archiveRules := flex.ArchiveRuleGet(lifecycleptr.Rules) expireRules := flex.ExpireRuleGet(lifecycleptr.Rules) nc_expRules := flex.Nc_exp_RuleGet(lifecycleptr.Rules) abort_mpuRules := flex.Abort_mpu_RuleGet(lifecycleptr.Rules) - if len(archiveRules) > 0 { + if len(archiveRules) > 0 && archiveFlag == true { + d.Set("archive_rule", archiveRules) } - if len(expireRules) > 0 { + if len(expireRules) > 0 && expireFlag == true { d.Set("expire_rule", expireRules) } - if len(nc_expRules) > 0 { + if len(nc_expRules) > 0 && ncFlag == true { d.Set("noncurrent_version_expiration", nc_expRules) } - if len(abort_mpuRules) > 0 { + if len(abort_mpuRules) > 0 && abortFlag == true { d.Set("abort_incomplete_multipart_upload_days", abort_mpuRules) } + } else { + fmt.Println("There is no lifecycle configuration on the bucket") } // Read retention rule diff --git a/ibm/service/cos/resource_ibm_cos_bucket_lifecycle_configuration.go b/ibm/service/cos/resource_ibm_cos_bucket_lifecycle_configuration.go new file mode 100644 index 0000000000..7237b7b2f3 --- /dev/null +++ b/ibm/service/cos/resource_ibm_cos_bucket_lifecycle_configuration.go @@ -0,0 +1,548 @@ +package cos + +import ( + // "encoding/json" + "context" + "fmt" + "strings" + "time" + + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/conns" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/flex" + "github.com/IBM-Cloud/terraform-provider-ibm/ibm/validate" + "github.com/IBM/ibm-cos-sdk-go/aws" + "github.com/IBM/ibm-cos-sdk-go/service/s3" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + validation "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func ResourceIBMCOSBucketLifecycleConfiguration() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceIBMCOSBucketLifecycleConfigurationCreate, + ReadContext: resourceIBMCOSBucketLifecycleConfigurationRead, + UpdateContext: resourceIBMCOSBucketLifecycleConfigurationUpdate, + DeleteContext: resourceIBMCOSBucketLifecycleConfigurationDelete, + Importer: &schema.ResourceImporter{}, + CustomizeDiff: customdiff.Sequence( + func(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { + return resourceValidateLifecycleRule(diff) + }, + ), + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(60 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(10 * time.Minute), + }, + Schema: map[string]*schema.Schema{ + "bucket_crn": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "COS bucket CRN", + }, + "bucket_location": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "COS bucket location", + }, + "endpoint_type": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.ValidateAllowedStringValues([]string{"public", "private", "direct"}), + Description: "COS endpoint type: public, private, direct", + Default: "public", + }, + "lifecycle_rule": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "abort_incomplete_multipart_upload": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "days_after_initiation": { + Type: schema.TypeInt, + ValidateFunc: validate.ValidateAllowedRangeInt(1, 3650), + Optional: true, + }, + }, + }, + }, + "expiration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "date": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.ValidateUTCFormat, + }, + "days": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validate.ValidateAllowedRangeInt(1, 3650), + Default: 0, // API returns 0 + }, + "expired_object_delete_marker": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + }, + }, + "filter": { + Type: schema.TypeList, + Required: true, + DiffSuppressFunc: suppressMissingFilterConfigurationBlock, + // IBM has filter as required parameter + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "prefix": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + "rule_id": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringLenBetween(1, 255), + }, + "noncurrent_version_expiration": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "noncurrent_days": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + }, + }, + }, + }, + "status": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.ValidateAllowedStringValues([]string{"enable", "disable"}), + }, + "transition": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "date": { + Type: schema.TypeString, + Optional: true, + ValidateFunc: validate.ValidateUTCFormat, + }, + "days": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validate.ValidateAllowedRangeInt(0, 3650), + }, + "storage_class": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validate.ValidateAllowedStringValues([]string{"GLACIER", + "ACCELERATED"}), + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func abortIncompleteMultipartUploadSet(abortIncompleteMultipartUpload map[string]interface{}) *s3.AbortIncompleteMultipartUpload { + var abort_incomplete_multipart_upload *s3.AbortIncompleteMultipartUpload + abortIncompleteMultipartUploadValue := s3.AbortIncompleteMultipartUpload{} + if len(abortIncompleteMultipartUpload) != 0 { + if daysSet, exists := abortIncompleteMultipartUpload["days_after_initiation"]; exists { + days := int64(daysSet.(int)) + abortIncompleteMultipartUploadValue.DaysAfterInitiation = aws.Int64(days) + } + abort_incomplete_multipart_upload = &abortIncompleteMultipartUploadValue + return abort_incomplete_multipart_upload + } else { + return nil + } +} + +func lifecycleExpirationSet(expiration []interface{}) *s3.LifecycleExpiration { + if len(expiration) == 0 { + return nil + } + + var result *s3.LifecycleExpiration + + resultValue := s3.LifecycleExpiration{} + + if expiration[0] == nil { + return result + } + + expirationMap := expiration[0].(map[string]interface{}) + if v, ok := expirationMap["date"].(string); ok && v != "" { + t, _ := time.Parse(time.RFC3339, v) + resultValue.Date = aws.Time(t) + } + + if v, ok := expirationMap["days"]; ok { + daysSet := int64(v.(int)) + if daysSet > 0 { + days := daysSet + resultValue.Days = aws.Int64(days) + } + } + + // This cannot be specified with Days or Date + if v, exist := expirationMap["expired_object_delete_marker"]; exist && resultValue.Date == nil && resultValue.Days == nil { + resultValue.ExpiredObjectDeleteMarker = aws.Bool(v.(bool)) + } + + result = &resultValue + + return result +} + +func lifecycleRuleFilterSet(filter []interface{}) *s3.LifecycleRuleFilter { + var result *s3.LifecycleRuleFilter + resultValue := s3.LifecycleRuleFilter{} + if filter[0] == nil { + resultValue.Prefix = aws.String("") + result = &resultValue + return result + } + filterMap := filter[0].(map[string]interface{}) + if v, ok := filterMap["prefix"].(string); ok { + resultValue.Prefix = aws.String(v) + } + result = &resultValue + return result +} + +func noncurrentVersionExpirationSet(noncurrentVersionExpiration map[string]interface{}) *s3.NoncurrentVersionExpiration { + var result *s3.NoncurrentVersionExpiration + resultValue := s3.NoncurrentVersionExpiration{} + if v, ok := noncurrentVersionExpiration["noncurrent_days"]; ok { + resultValue.NoncurrentDays = aws.Int64(int64(v.(int))) + } + result = &resultValue + return result +} + +func transitionsSet(transitions []interface{}) []*s3.Transition { + if len(transitions) == 0 { + return nil + } + var results []*s3.Transition + var transition = s3.Transition{} + if transitions[0] == nil { + return results + } + transitionMap := transitions[0].(map[string]interface{}) + if v, ok := transitionMap["date"].(string); ok && v != "" { + t, _ := time.Parse(time.RFC3339, v) + transition.Date = aws.Time(t) + } + + // Only one of "date" and "days" can be configured + // so only set the transition.Days value when transition.Date is nil + // By default, transitionMap["days"] = 0 if not explicitly configured in terraform. + if v, ok := transitionMap["days"]; ok && v.(int) >= 0 && transition.Date == nil { + transition.Days = aws.Int64(int64(v.(int))) + } + if v, ok := transitionMap["storage_class"].(string); ok && v != "" { + transition.StorageClass = aws.String(v) + } + results = append(results, &transition) + + return results +} + +func lifecycleConfigurationSet(lifecycleConfigurationList []interface{}) []*s3.LifecycleRule { + var lifecycleRules []*s3.LifecycleRule + for _, lifecycleRuleMapRaw := range lifecycleConfigurationList { + lifecycleRuleMap, ok := lifecycleRuleMapRaw.(map[string]interface{}) + if !ok { + continue + } + lifecycleRule := s3.LifecycleRule{} // single rule to be appended to the list of lifecycle rules + // check for abort incomplete multipart + if v, ok := lifecycleRuleMap["abort_incomplete_multipart_upload"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + lifecycleRule.AbortIncompleteMultipartUpload = abortIncompleteMultipartUploadSet(v[0].(map[string]interface{})) + } + // check for expiration + if v, ok := lifecycleRuleMap["expiration"].([]interface{}); ok && len(v) > 0 { + lifecycleRule.Expiration = lifecycleExpirationSet(v) + } + // check for filter (required) + if v, ok := lifecycleRuleMap["filter"].([]interface{}); ok && len(v) > 0 { + lifecycleRule.Filter = lifecycleRuleFilterSet(v) + } + // check for rule id (required) + if v, ok := lifecycleRuleMap["rule_id"].(string); ok { + lifecycleRule.ID = aws.String(v) + } + // check for noncurrent version expiration + if v, ok := lifecycleRuleMap["noncurrent_version_expiration"].([]interface{}); ok && len(v) > 0 && v[0] != nil { + lifecycleRule.NoncurrentVersionExpiration = noncurrentVersionExpirationSet(v[0].(map[string]interface{})) + } + // check for status + if v, ok := lifecycleRuleMap["status"].(string); ok && v != "" { + if v == "enable" { + lifecycleRule.Status = aws.String("Enabled") + } else if v == "disable" { + lifecycleRule.Status = aws.String("Disabled") + } + + } + + // check for transition + if v, ok := lifecycleRuleMap["transition"].([]interface{}); ok && len(v) > 0 { + lifecycleRule.Transitions = transitionsSet(v) + } + + lifecycleRules = append(lifecycleRules, &lifecycleRule) + } + + return lifecycleRules +} + +func resourceIBMCOSBucketLifecycleConfigurationCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + bucketCRN := d.Get("bucket_crn").(string) + bucketName := strings.Split(bucketCRN, ":bucket:")[1] + instanceCRN := fmt.Sprintf("%s::", strings.Split(bucketCRN, ":bucket:")[0]) + bucketLocation := d.Get("bucket_location").(string) + endpointType := d.Get("endpoint_type").(string) + bxSession, err := meta.(conns.ClientSession).BluemixSession() + if err != nil { + return diag.Errorf("%v", err) + } + s3Client, err := getS3ClientSession(bxSession, bucketLocation, endpointType, instanceCRN) + lifecycleRule := d.Get("lifecycle_rule") + rules := lifecycleConfigurationSet(lifecycleRule.([]interface{})) // setting each lifecycle rule + if err != nil { + return diag.Errorf("Failed to read lifecycle configuration for COS bucket %s, %v", bucketName, err) + } + putBucketLifecycleConfigurationInput := s3.PutBucketLifecycleConfigurationInput{ + Bucket: aws.String(bucketName), + LifecycleConfiguration: &s3.LifecycleConfiguration{ + Rules: rules, + }, + } + _, err = s3Client.PutBucketLifecycleConfiguration(&putBucketLifecycleConfigurationInput) + if err != nil { + return diag.Errorf("Failed to put Lifecycle configuration on the COS bucket %s, %v", bucketName, err) + } + bktID := fmt.Sprintf("%s:%s:%s:meta:%s:%s", strings.Replace(instanceCRN, "::", "", -1), "bucket", bucketName, bucketLocation, endpointType) + d.SetId(bktID) + return resourceIBMCOSBucketLifecycleConfigurationUpdate(ctx, d, meta) +} + +func resourceIBMCOSBucketLifecycleConfigurationUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + bucketCRN := d.Get("bucket_crn").(string) + bucketName := strings.Split(bucketCRN, ":bucket:")[1] + instanceCRN := fmt.Sprintf("%s::", strings.Split(bucketCRN, ":bucket:")[0]) + bucketLocation := d.Get("bucket_location").(string) + endpointType := d.Get("endpoint_type").(string) + bxSession, err := meta.(conns.ClientSession).BluemixSession() + if err != nil { + return diag.Errorf("%v", err) + } + s3Client, err := getS3ClientSession(bxSession, bucketLocation, endpointType, instanceCRN) + if d.HasChange("lifecycle_rule") { + lifecycleRule := d.Get("lifecycle_rule") + rules := lifecycleConfigurationSet(lifecycleRule.([]interface{})) + if err != nil { + return diag.Errorf("Failed to read lifecycle configuration for COS bucket %s, %v", bucketName, err) + } + putBucketLifecycleConfigurationInput := s3.PutBucketLifecycleConfigurationInput{ + Bucket: aws.String(bucketName), + LifecycleConfiguration: &s3.LifecycleConfiguration{ + Rules: rules, + }, + } + _, err = s3Client.PutBucketLifecycleConfiguration(&putBucketLifecycleConfigurationInput) + if err != nil { + return diag.Errorf("Failed to put Lifecycle configuration on the COS bucket %s, %v", bucketName, err) + } + } + return resourceIBMCOSBucketLifecycleConfigurationRead(ctx, d, meta) +} + +func resourceIBMCOSBucketLifecycleConfigurationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + bucketCRN := parseWebsiteId(d.Id(), "bucketCRN") + bucketName := parseWebsiteId(d.Id(), "bucketName") + bucketLocation := parseWebsiteId(d.Id(), "bucketLocation") + instanceCRN := parseWebsiteId(d.Id(), "instanceCRN") + endpointType := parseWebsiteId(d.Id(), "endpointType") + d.Set("bucket_crn", bucketCRN) + d.Set("bucket_location", bucketLocation) + if endpointType != "" { + d.Set("endpoint_type", endpointType) + } + bxSession, err := meta.(conns.ClientSession).BluemixSession() + if err != nil { + return diag.Errorf("%v", err) + } + s3Client, err := getS3ClientSession(bxSession, bucketLocation, endpointType, instanceCRN) + if err != nil { + return diag.Errorf("%v", err) + } + //getBucketConfiguration + const ( + lifecycleConfigurationRulesSteadyTimeout = 2 * time.Minute + ) + getLifecycleConfigurationInput := &s3.GetBucketLifecycleConfigurationInput{ + Bucket: aws.String(bucketName), + } + var output *s3.GetBucketLifecycleConfigurationOutput + + // Adding a retry to overcome the NoSuchLifecycleConfiguration error as it takes time for the lifecycle rules to get applied. + err = resource.Retry(lifecycleConfigurationRulesSteadyTimeout, func() *resource.RetryError { + var err error + output, err = s3Client.GetBucketLifecycleConfiguration(getLifecycleConfigurationInput) + + if d.IsNewResource() && err != nil && strings.Contains(err.Error(), "NoSuchLifecycleConfiguration: The lifecycle configuration does not exist") { + + return resource.RetryableError(err) + } + if err != nil { + return resource.NonRetryableError(err) + } + return nil + }) + if conns.IsResourceTimeoutError(err) { + output, err = s3Client.GetBucketLifecycleConfiguration(getLifecycleConfigurationInput) + } + if err != nil { + return diag.Errorf("[ERROR] Error getting Lifecycle Configuration for the bucket %s", bucketName) + } + var outputptr *s3.LifecycleConfiguration + outputptr = (*s3.LifecycleConfiguration)(output) + if err != nil && !strings.Contains(err.Error(), "AccessDenied: Access Denied") { + return diag.Errorf("%v", err) + } + if outputptr.Rules != nil { + lifecycleConfiguration := flex.LifecylceRuleGet(outputptr.Rules) + if len(lifecycleConfiguration) > 0 { + d.Set("lifecycle_rule", lifecycleConfiguration) + } + } else { + d.SetId("") + } + return nil +} + +func resourceIBMCOSBucketLifecycleConfigurationDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + bucketName := parseWebsiteId(d.Id(), "bucketName") + bucketLocation := parseWebsiteId(d.Id(), "bucketLocation") + instanceCRN := parseWebsiteId(d.Id(), "instanceCRN") + endpointType := parseWebsiteId(d.Id(), "endpointType") + bxSession, err := meta.(conns.ClientSession).BluemixSession() + if err != nil { + return diag.Errorf("%v", err) + } + s3Client, err := getS3ClientSession(bxSession, bucketLocation, endpointType, instanceCRN) + if err != nil { + return diag.Errorf("%v", err) + } + deleteBucketLifecycleInput := &s3.DeleteBucketLifecycleInput{ + Bucket: aws.String(bucketName), + } + _, err = s3Client.DeleteBucketLifecycle(deleteBucketLifecycleInput) + if err != nil { + return diag.Errorf("failed to delete the Lifecycle configuration on the COS bucket %s, %v", bucketName, err) + } + return nil +} + +func parseLifecycleId(id string, info string) string { + bucketCRN := strings.Split(id, ":meta:")[0] + meta := strings.Split(id, ":meta:")[1] + if info == "bucketName" { + return strings.Split(bucketCRN, ":bucket:")[1] + } + if info == "instanceCRN" { + return fmt.Sprintf("%s::", strings.Split(bucketCRN, ":bucket:")[0]) + } + if info == "bucketCRN" { + return bucketCRN + } + if info == "bucketLocation" { + return strings.Split(meta, ":")[0] + } + if info == "endpointType" { + return strings.Split(meta, ":")[1] + } + if info == "keyName" { + return strings.Split(meta, ":key:")[1] + } + return parseBucketId(bucketCRN, info) +} + +func resourceValidateLifecycleRule(diff *schema.ResourceDiff) error { + if lifecycle, ok := diff.GetOk("lifecycle_rule"); ok { + lifecycle_list := lifecycle.([]interface{}) + for _, l := range lifecycle_list { + lifecycleMap, _ := l.(map[string]interface{}) + expiration := lifecycleMap["expiration"] + expirationParameterCounter := 0 + if expiration != nil { + expirationList, _ := expiration.([]interface{}) + for _, e := range expirationList { + expirationMap, _ := e.(map[string]interface{}) + if val, days_exist := expirationMap["days"]; days_exist && val.(int) != 0 { + expirationParameterCounter++ + } + if val, date_exist := expirationMap["date"]; date_exist && val != "" { + expirationParameterCounter++ + } + if val, expired_object_delete_marker_exist := expirationMap["expired_object_delete_marker"]; expired_object_delete_marker_exist && val.(bool) != false { + expirationParameterCounter++ + } + if expirationParameterCounter > 1 { + return fmt.Errorf("[ERROR] The expiry 3 action elements (Days, Date, ExpiredObjectDeleteMarker) are all mutually exclusive. These can not be used with each other. Please set one expiry element on the same rule of expiration.") + } + } + } + + } + } + return nil +} + +func suppressMissingFilterConfigurationBlock(k, old, new string, d *schema.ResourceData) bool { + if strings.HasSuffix(k, "filter.#") { + oraw, nraw := d.GetChange(k) + o, n := oraw.(int), nraw.(int) + if o == 1 && n == 0 { + return true + } + if o == 1 && n == 1 { + return old == "1" && new == "0" + } + return false + } + return false +} diff --git a/ibm/service/cos/resource_ibm_cos_bucket_lifecycle_configuration_test.go b/ibm/service/cos/resource_ibm_cos_bucket_lifecycle_configuration_test.go new file mode 100644 index 0000000000..671b55870e --- /dev/null +++ b/ibm/service/cos/resource_ibm_cos_bucket_lifecycle_configuration_test.go @@ -0,0 +1,1255 @@ +package cos_test + +import ( + "fmt" + "regexp" + "testing" + // "time" + + acc "github.com/IBM-Cloud/terraform-provider-ibm/ibm/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Days(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Days(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.days", "1"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Date(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDate := "2024-09-05T00:00:00Z" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Date(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDate), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.date", expirationDate), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_With_Days_Glacier(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + transitionDays := 1 + tStorageclass := "GLACIER" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Days(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, transitionDays, tStorageclass), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.days", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.storage_class", "GLACIER"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_With_Date_Glacier(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + transitionDate := "2024-09-05T00:00:00Z" + tStorageclass := "GLACIER" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Date(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, transitionDate, tStorageclass), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.date", transitionDate), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.storage_class", "GLACIER"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_With_Days_Accelerated(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + transitionDays := 1 + tStorageclass := "ACCELERATED" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Days(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, transitionDays, tStorageclass), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.days", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.storage_class", "ACCELERATED"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Abort_Incomplete_Multipart_Upload(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := 2 + daysAfterInitiation := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Abort_Incomplete_Multipart_Upload(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays, daysAfterInitiation), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.days", "2"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.abort_incomplete_multipart_upload.0.days_after_initiation", "1"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Abort_Incomplete_Multipart_Upload(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + daysAfterInitiation := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Abort_Incomplete_Multipart_Upload(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, daysAfterInitiation), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.abort_incomplete_multipart_upload.0.days_after_initiation", "1"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_With_Date_Accelerated(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + transitionDate := "2024-09-05T00:00:00Z" + tStorageclass := "ACCELERATED" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Date(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, transitionDate, tStorageclass), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.date", transitionDate), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.storage_class", "ACCELERATED"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Transition(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := 2 + transitionDays := 1 + tStorageClass := "GLACIER" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Transition(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays, transitionDays, tStorageClass), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.days", "2"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.days", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.storage_class", "GLACIER"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_With_Abort_Incomplete_Multipart_Upload(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + transitionDays := 2 + tStorageClass := "GLACIER" + initiationDays := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Abort_Incomplete_Multipart_Upload(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, transitionDays, tStorageClass, initiationDays), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.days", "2"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.storage_class", "GLACIER"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.abort_incomplete_multipart_upload.0.days_after_initiation", "1"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Noncurrent_Version_Expiration(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := 2 + nonCurrentDays := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Noncurrent_Version_Expiration(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays, nonCurrentDays), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.days", "2"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.noncurrent_version_expiration.0.noncurrent_days", "1"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Expired_Object_Delete_Marker(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Expired_Object_Delete_Marker(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.expired_object_delete_marker", "true"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Expired_Object_Delete_Marker_On_Versioning_enable_Bucket(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Expired_Object_Delete_Marker_On_Versioning_Bucket(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.expired_object_delete_marker", "true"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_Transition_Abort_Incomplete_Multipart_Upload_All(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := 3 + transitionDays := 2 + tStorageclass := "GLACIER" + daysAfterInitiation := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_Transition_Abort_Incomplete_Multipart_Upload(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays, transitionDays, tStorageclass, daysAfterInitiation), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.days", "3"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.days", "2"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.transition.0.storage_class", "GLACIER"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.abort_incomplete_multipart_upload.0.days_after_initiation", "1"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_With_No_Filter(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_With_No_Filter(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays), + ExpectError: regexp.MustCompile("Error: Insufficient filter blocks"), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Status_Disabled(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := 1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Status_disable(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.days", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.status", "disable"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Multiple_Rules(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays1 := 2 + daysAfterInitiation1 := 1 + expirationDays2 := 4 + daysAfterInitiation2 := 3 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Multiple_Rules(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays1, daysAfterInitiation1, expirationDays2, daysAfterInitiation2), + Check: resource.ComposeAggregateTestCheckFunc( + testAccCheckIBMCosBucketExists("ibm_resource_instance.instance", "ibm_cos_bucket.bucket", bucketRegionType, bucketRegion, bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "bucket_name", bucketName), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "storage_class", bucketClass), + resource.TestCheckResourceAttr("ibm_cos_bucket.bucket", "region_location", bucketRegion), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.#", "2"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.expiration.0.days", "2"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.0.abort_incomplete_multipart_upload.0.days_after_initiation", "1"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.1.expiration.0.days", "4"), + resource.TestCheckResourceAttr("ibm_cos_bucket_lifecycle_configuration.lifecycle", "lifecycle_rule.1.abort_incomplete_multipart_upload.0.days_after_initiation", "3"), + ), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Expiration_With_Invalid_Days(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + expirationDays := -1 + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Days(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, expirationDays), + ExpectError: regexp.MustCompile(`"lifecycle_rule.0.expiration.0.days" must contain a valid int value should be in range\(1, 3650\), got -1`), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_With_Invalid_Days(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + transitionDays := -1 + tStorageclass := "GLACIER" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Days(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, transitionDays, tStorageclass), + ExpectError: regexp.MustCompile(`"lifecycle_rule.0.transition.0.days" must contain a valid int value should be in range\(0, 3650\), got -1`), + }, + }, + }) +} + +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_With_Invalid_Storage_Class(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + transitionDays := 1 + tStorageclass := "Invalid" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Days(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass, transitionDays, tStorageclass), + ExpectError: regexp.MustCompile(`"lifecycle_rule.0.transition.0.storage_class" must contain a value from \[\]string\{"GLACIER", "ACCELERATED"\}, got "Invalid`), + }, + }, + }) +} +func TestAccIBMCosBucket_Lifecycle_Configuration_Transition_Multiple_Rules(t *testing.T) { + serviceName := fmt.Sprintf("terraform_%d", acctest.RandIntRange(10, 100)) + bucketName := fmt.Sprintf("terraform-lifecycle-configuration%d", acctest.RandIntRange(10, 100)) + bucketRegion := "us-south" + bucketClass := "standard" + bucketRegionType := "region_location" + + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + Providers: acc.TestAccProviders, + CheckDestroy: testAccCheckIBMCosBucketDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Multiple_Rules(serviceName, bucketName, bucketRegionType, bucketRegion, bucketClass), + ExpectError: regexp.MustCompile("MalformedXML: The XML you provided was not well-formed or did not validate against our published schema."), + }, + }, + }) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Days(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Date(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, date string) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + date = "%s" + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, date) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Days(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int, tStorageclass string) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + transition{ + days = "%d" + storage_class = "%s" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days, tStorageclass) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Date(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, date string, tStorageclass string) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + transition{ + date = "%s" + storage_class = "%s" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, date, tStorageclass) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Abort_Incomplete_Multipart_Upload(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int, initiationDays int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + abort_incomplete_multipart_upload{ + days_after_initiation = "%d" + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days, initiationDays) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Abort_Incomplete_Multipart_Upload(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, initiationDays int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + abort_incomplete_multipart_upload{ + days_after_initiation = "%d" + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, initiationDays) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Transition(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int, tDays int, tStorageClass string) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + transition{ + days = "%d" + storage_class = "%s" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days, tDays, tStorageClass) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Abort_Incomplete_Multipart_Upload(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, tDays int, tStorageClass string, initiationDays int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + transition{ + days = "%d" + storage_class = "%s" + } + abort_incomplete_multipart_upload{ + days_after_initiation = "%d" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, tDays, tStorageClass, initiationDays) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Noncurrent_Version_Expiration(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int, ncDays int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + noncurrent_version_expiration{ + noncurrent_days = "%d" + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days, ncDays) +} +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Expired_Object_Delete_Marker(cosServiceName string, bucketName string, regiontype string, region string, storageClass string) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + expired_object_delete_marker = true + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_With_Expired_Object_Delete_Marker_On_Versioning_Bucket(cosServiceName string, bucketName string, regiontype string, region string, storageClass string) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + object_versioning { + enable = true + } + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + expired_object_delete_marker = true + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Expiration_Transition_Abort_Incomplete_Multipart_Upload(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int, tDays int, tStorageClass string, initiationDays int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + transition{ + days = "%d" + storage_class = "%s" + } + abort_incomplete_multipart_upload{ + days_after_initiation = "%d" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days, tDays, tStorageClass, initiationDays) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_With_No_Filter(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + rule_id = "id" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Status_disable(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "disable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Multiple_Rules(cosServiceName string, bucketName string, regiontype string, region string, storageClass string, days1 int, initiationDays1 int, days2 int, initiationDays2 int) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + expiration{ + days = "%d" + } + abort_incomplete_multipart_upload{ + days_after_initiation = "%d" + } + filter { + prefix = "foo" + } + rule_id = "id1" + status = "enable" + + } + lifecycle_rule { + expiration{ + days = "%d" + } + abort_incomplete_multipart_upload{ + days_after_initiation = "%d" + } + filter { + prefix = "bar" + } + rule_id = "id2" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass, days1, initiationDays1, days2, initiationDays2) +} + +func testAccCheckIBMCosBucket_Lifecycle_Configuration_Transition_With_Multiple_Rules(cosServiceName string, bucketName string, regiontype string, region string, storageClass string) string { + + return fmt.Sprintf(` + data "ibm_resource_group" "cos_group" { + name = "Default" + } + + resource "ibm_resource_instance" "instance" { + name = "%s" + service = "cloud-object-storage" + plan = "standard" + location = "global" + resource_group_id = data.ibm_resource_group.cos_group.id + } + resource "ibm_cos_bucket" "bucket" { + bucket_name = "%s" + resource_instance_id = ibm_resource_instance.instance.id + region_location = "%s" + storage_class = "%s" + } + resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.bucket.crn + bucket_location = ibm_cos_bucket.bucket.region_location + lifecycle_rule { + transition{ + days = 1 + storage_class = "GLACIER" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + } + lifecycle_rule { + transition{ + days = 3 + storage_class = "ACCELERATED" + } + filter { + prefix = "" + } + rule_id = "id2" + status = "enable" + + } + } + `, cosServiceName, bucketName, region, storageClass) +} diff --git a/ibm/validate/validators.go b/ibm/validate/validators.go index dd9df34b11..775ed53962 100644 --- a/ibm/validate/validators.go +++ b/ibm/validate/validators.go @@ -79,6 +79,24 @@ func ValidBucketLifecycleTimestamp(v interface{}, k string) (ws []string, errors return } +func ValidateUTCFormat(v interface{}, k string) (ws []string, errors []error) { + // Assert v to be a string + value, ok := v.(string) + if !ok { + errors = append(errors, fmt.Errorf("value must be a string, got %T", v)) + return + } + + // Parse the string as RFC3339 time format + _, err := time.Parse(time.RFC3339, value) + if err != nil { + errors = append(errors, fmt.Errorf("value must be in RFC3339 format %q. Example: %s", time.RFC3339, err)) + return + } + + return +} + func validateRegexpLen(min, max int, regex string) schema.SchemaValidateFunc { return func(v interface{}, k string) (ws []string, errors []error) { value := v.(string) diff --git a/website/docs/r/cos_bucket.html.markdown b/website/docs/r/cos_bucket.html.markdown index 812d8e93c8..da0e434825 100644 --- a/website/docs/r/cos_bucket.html.markdown +++ b/website/docs/r/cos_bucket.html.markdown @@ -457,11 +457,46 @@ resource "ibm_cos_bucket" "cos_bucket" { } ``` +# ibm_cos_bucket_lifecycle_configuration + +Provides an independent resource to manage the lifecycle configuration for a bucket.For more information please refer to [`ibm_cos_bucket_lifecycle_configuration`](https://registry.terraform.io/providers/IBM-Cloud/ibm/latest/docs/resources/ibm_cos_bucket_lifecycle_configuration) + +## Example usage + +```terraform +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 1 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "Enabled" + + } +} +``` + +**Note:** +To manage changes of Lifecycle rules to an cos bucket, use the ibm_cos_bucket_lifecycle_configuration resource instead. If you use `expire_rule` , `archive_rule` , `noncurrent_version_expiration`, `abort_incomplete_multipart_upload_days` on an ibm_cos_bucket, Terraform will assume management over the full set of Lifecycle rules for the cos bucket, treating additional Lifecycle rules as drift. For this reason, lifecycle_rule cannot be mixed with the external ibm_cos_bucket_lifecycle_configuration resource for a given S3 bucket. ## Argument reference Review the argument references that you can specify for your resource. - `abort_incomplete_multipart_upload_days` (Optional,List) Nested block with the following structure. + +(Recommended) Use `ibm_cos_bucket_lifecycle_configuration` instead of `abort_incomplete_multipart_upload_days` to manage the lifecycle abort incomplete multipart upload days on cos bucket. Nested scheme for `abort_incomplete_multipart_upload_days`: - `days_after_initiation` - (Optional, integer) Specifies the number of days that govern the automatic cancellation of part upload. Clean up incomplete multi-part uploads after a period of time. Must be a value greater than 0 and less than 3650. @@ -486,7 +521,9 @@ Review the argument references that you can specify for your resource. - `management_events`- (Optional, bool) If set to **true**, all bucket management events will be sent to Activity Tracker.This field only applies if `activity_tracker_crn` is not populated. - `archive_rule` - (Required, List) Nested archive_rule block has following structure. - + + (Recommended) Use `ibm_cos_bucket_lifecycle_configuration` instead of `archive rule` to manage the lifecycle archive rule on cos bucket. + Nested scheme for `archive_rule`: - `days` - (Required, integer) Specifies the number of days when the specific rule action takes effect. - `enable` - (Required, bool) Specifies archive rule status either `enable` or `disable` for a bucket. @@ -500,7 +537,8 @@ Review the argument references that you can specify for your resource. - `cross_region_location` - (Optional, string) Specify the cross-regional bucket location. Supported values are `us`, `eu`, and `ap`. If you use this parameter, do not set `single_site_location` or `region_location` at the same time. - `endpoint_type`- (Optional, string) The type of the endpoint either `public` or `private` or `direct` to be used for buckets. Default value is `public`. - `expire_rule` - (Required, List) An expiration rule deletes objects after a defined period (from the object creation date). see [lifecycle actions](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-versioning). Nested expire_rule block has following structure. - + + (Recommended) Use `ibm_cos_bucket_lifecycle_configuration` instead of `expire_rule` to manage the lifecycle expire rule on cos bucket. Nested scheme for `expire_rule`: - `days` - (Optional, integer) Specifies the number of days when the specific rule action takes effect. - `date` - (Optional, string) After the specifies date , the current version of objects in your bucket expires. @@ -537,7 +575,12 @@ Review the argument references that you can specify for your resource. - `request_metrics_enabled` : (Optional, bool) If set to **true**, all request metrics (i.e. `rest.object.head`) will be sent to the monitoring service. - `usage_metrics_enabled` : (Optional, bool) If set to **true**, all usage metrics (i.e. `bytes_used`) will be sent to the monitoring service. -- `noncurrent_version_expiration` - (Required, List) lifecycle has a versioning related expiration action: non-current version expiration. This can remove old versions of objects after they've been non-current for a specified number of days which is specified with a NoncurrentDays parameter on the rule. see [lifecycle actions](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-versioning). Nested noncurrent_version_expiration block has following structure. +- `noncurrent_version_expiration` - (Required, List) lifecycle has a versioning related expiration action: non-current version expiration. This can remove old versions of objects after they've been non-current for a specified number of days which is specified with a NoncurrentDays parameter on the rule. see [lifecycle actions](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-versioning). + + (Recommended) Use `ibm_cos_bucket_lifecycle_configuration` instead of `noncurrent_version_expiration` to manage the lifecycle noncurrent version expiration on cos bucket. + + Nested noncurrent_version_expiration block has following structure. + Nested scheme for `noncurrent_version_expiration`: - `enable` - (Requried, bool) A rule can either be `enabled` or `disabled`. A rule is active only when enabled. diff --git a/website/docs/r/cos_bucket_lifecycle_configuration.markdown b/website/docs/r/cos_bucket_lifecycle_configuration.markdown new file mode 100644 index 0000000000..093f3c1bdc --- /dev/null +++ b/website/docs/r/cos_bucket_lifecycle_configuration.markdown @@ -0,0 +1,324 @@ +--- + +subcategory: "Object Storage" +layout: "ibm" +page_title: "IBM : Cloud Object Storage Lifecycle Configuration" +description: + "Manages IBM Cloud Object Storage Lifecycle Configuration" +--- + +# ibm_cos_bucket_lifecycle_configuration +This is the recommended way of managing the lifecycle configuration for a bucket. This provides an independent resource to manage the lifecycle configuration for a bucket.A lifecycle configuration consists of an lifecycle rule , each rule has a unique id , status and an action. Action can be of 2 types - [transition](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-archive) and [expiration](https://cloud.ibm.com/docs/cloud-object-storage?topic=cloud-object-storage-expiry).It also consists of noncurrent version expiration and abort incomplete multipart upload.A lifecycle sonfiguration can have multiple expiration rules but only one transition rule. + + +## Example usage + +# Adding lifecycle configuration with expiration. + +Adding lifecycle configuration with expiration and prefix filter. + +```terraform + + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 1 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } +} + +``` + +# Adding lifecycle configuration with transition. + +Adding lifecycle configuration with transition. + +```terraform + + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + transition{ + days = 1 + storage_class = "GLACIER" + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } +} + +``` +# Adding lifecycle configuration with abort incomplete multipart upload. + +```terraform + + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + abort_incomplete_multipart_upload{ + days_after_initiation = 1 + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } +} + +``` +# Adding lifecycle configuration with non current version expiration. + +```terraform + + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + noncurrent_version_expiration{ + noncurrent_days = "1 + } + filter { + prefix = "" + } + rule_id = "id" + status = "enable" + + } +} + +``` +# Adding lifecycle configuration with multiple rules. + +```terraform + + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + +} +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 1 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + } + lifecycle_rule { + expiration{ + days = 2 + } + filter { + prefix = "bar" + } + rule_id = "id2" + status = "enable" + } +} + +``` + +# Switching from legacy lifecycle rules to ibm_cos_bucket_lifecycle_configuration : + + +**Note:** +If you use legacy `expire_rule` , `archive_rule` , `noncurrent_version_expiration`, `abort_incomplete_multipart_upload_days` on an ibm_cos_bucket, Terraform will assume management over the full set of Lifecycle rules for the bucket, treating additional Lifecycle rules as drift. For this reason, legacy rules cannot be mixed with the external ibm_cos_bucket_lifecycle_configuration resource for a given cos bucket.Users that want to continue using the legacy `expire_rule` , `archive_rule` , `noncurrent_version_expiration`, `abort_incomplete_multipart_upload_days`. + +In case you want to switch from the legacy lifecycle rules to the new resource for an existing cos bucket with existing legacy lifecycle rules , please follow the steps below + +## Example usage +For example if there is a cos bucket existing in the `.tfstate` file with `expire_rule` applied. +```terraform + +# Existing Cos bucket configuration + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + expire_rule { + rule_id = "id" + enable = true + days = 45 + prefix = "foo" + } + +} + +``` +Step 1 : Add new `ibm_cos_bucket_lifecycle_configuration` with same configuration as exisitng. + +```terraform + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class + expire_rule { + rule_id = "id" + enable = true + days = 45 + prefix = "foo" + } +} + +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 45 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } +} + +``` +Step 2 : Run `terraform apply` + +Step 3 : Remove the old configuration from the `ibm_cos_bucket` block + +```terraform + +resource "ibm_cos_bucket" "cos_bucket" { + bucket_name = var.bucket_name + resource_instance_id = ibm_resource_instance.cos_instance.id + region_location = var.regional_loc + storage_class = var.standard_storage_class +} + +resource "ibm_cos_bucket_lifecycle_configuration" "lifecycle" { + bucket_crn = ibm_cos_bucket.cos_bucket.crn + bucket_location = ibm_cos_bucket.cos_bucket.region_location + lifecycle_rule { + expiration{ + days = 45 + } + filter { + prefix = "foo" + } + rule_id = "id" + status = "enable" + + } + } +} + +``` + +## Argument reference +Review the argument references that you can specify for your resource. +- `bucket_crn` - (Required, Forces new resource, String) The CRN of the COS bucket. +- `bucket_location` - (Required, Forces new resource, String) The location of the COS bucket. +- `endpoint_type`- (Optional, String) The type of the endpoint either `public` or `private` or `direct` to be used for buckets. Default value is `public`. +- `lifecycle_rule`- (Required, List) Nested block have the following structure: + + Nested scheme for `lifecycle_rule`: + - `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. + - `transition`- (Optional) Configuration block that specifies the transition for of the object. + - `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. + - `noncurrent_version`- (Optinal) Configuration block that specifies when noncurrent object versions expire. + - `id`- (Required) Unique id for the rule. + - `filter`- (Required) Configuration block used to identify objects that a Lifecycle Rule applies to.If not specified, the rule will default to using prefix. + - `status`- (Required) Whether the rule is currently being applied. Valid values: enable or disable. + + Nested scheme for `expiration`: + - `days`- (Optional) Days, of the objects that are subject to the rule. The value must be a non-zero positive integer. + - `date`- (Optional) Date the object is to be deleted. The date value must be in RFC3339 full-date format. + - `expired_object_delete_marker`- (Optional, Conflicts with date and days) Indicates whether ibm will remove a delete marker with no noncurrent versions. + + Nested scheme for `transition`: + - `days`- (Optional) Number of days after creation when objects are transitioned to the specified storage class. The value must be a positive integer. If both days and date are not specified, defaults to 0. + - `date`- (Optional) Date objects are transitioned to the specified storage class. The date value must be in RFC3339 full-date format. + - `storage_class`- (Optional) Class of storage used to store the object. Valid Values: GLACIER,ACCELERATED. + + Nested scheme for `noncurrent_version_expiration`: + - `noncurrent_days` - (Optional) Number of days an object is noncurrent before lifecycle action is performed. Must be a positive integer. + + Nested scheme for `abort_incomplete_multipart_upload`: + - `days_after_initiation` - Number of days after which incomplete multipart uploads are aborted. + + + ## Import IBM COS lifecycle configuration +The lifecycle configurations rules for a bucket can be imported into an `ibm_cos_bucket_lifecycle_configuration` resource for a particular bucket using the bucket id. + +id = `$CRN:meta:$buckettype:$bucketlocation` + +**Syntax** + +``` +$ terraform import ibm_cos_bucket_lifecycle_configuration.lifecycle `$CRN:meta:$buckettype:$bucketlocation` + +``` + +**Example** + +``` + +$ terraform import ibm_cos_bucket_lifecycle_configuration.lifecycle crn:v1:bluemix:public:cloud-object-storage:global:a/4ea1882a2d3401ed1e459979941966ea:31fa970d-51d0-4b05-893e-251cba75a7b3:bucket:mybucketname:meta:crl:eu:public + +``` \ No newline at end of file