From b7906897f22d031239e2ece1a1e49fbe67ab5e89 Mon Sep 17 00:00:00 2001 From: Margubur Rahman Date: Thu, 3 Apr 2025 19:44:03 +0530 Subject: [PATCH 1/4] Provide support for deleting pending caches present on bucket, This would help our customers to destory their bucket consisting of caches managed outside terraform without any other step --- .../storage/resource_storage_bucket.go.tmpl | 112 +++++++++++++++++- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl index 53084a0b95a1..a98d5401824e 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl +++ b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl @@ -101,7 +101,7 @@ func ResourceStorageBucket() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: false, - Description: `When deleting a bucket, this boolean option will delete all contained objects. If you try to delete a bucket that contains objects, Terraform will fail that run.`, + Description: `When deleting a bucket, this boolean option will delete all contained objects, or anywhereCaches (if any). If you try to delete a bucket that contains objects or anywhereCaches, Terraform will fail that run.`, }, "labels": { @@ -590,6 +590,98 @@ func labelKeyValidator(val interface{}, key string) (warns []string, errs []erro return } +func getAnywhereCacheListResult(config *transport_tpg.Config, bucket string) ([]interface{}, error) { + // Define the cache list URL + cacheListUrl := fmt.Sprintf("https://storage.googleapis.com/storage/v1/b/%s/anywhereCaches/", bucket) + + // Send request to get resource list + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: cacheListUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + return nil, err + } + + resourceList, ok := res["items"] + if !ok { + return nil, nil // No cache exists, return nil list and no error + } + + rl, ok := resourceList.([]interface{}) + if !ok { + return nil, fmt.Errorf("unexpected type for resource list: %T", resourceList) + } + + return rl, nil +} + +func deleteAnywhereCacheIfAny(config *transport_tpg.Config, bucket string) error { + // Get the initial list of Anywhere Caches + cacheList, err := getAnywhereCacheListResult(config, bucket) + if err != nil { + return err + } + + // If no cache exists initially, return early + if len(cacheList) == 0 { + return nil + } + + // Iterate over each object in the resource list + for _, item := range cacheList { + // Ensure the item is a map + obj, ok := item.(map[string]interface{}) + if !ok { + return fmt.Errorf("unexpected type for resource list item: %T", item) + } + + // Check the state of the object + state, ok := obj["state"].(string) + if !ok { + continue // If state is not a string, skip this item + } + if state != "running" && state != "paused" { + continue + } + + // Disable the cache if state is running or paused + anywhereCacheId, ok := obj["anywhereCacheId"].(string) + if !ok { + return fmt.Errorf("missing or invalid anywhereCacheId: %v", obj) + } + disableUrl := fmt.Sprintf("https://storage.googleapis.com/storage/v1/b/%s/anywhereCaches/%s/disable", bucket, anywhereCacheId) + _, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: config.Project, + RawURL: disableUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + return err + } + } + time.Sleep(80 * time.Minute) // It takes around 70 minutes of time for cache to finally delete post it disable time. + + // Post this time, we check again! + // Get the list of Anywhere Caches after the sleep + cacheList, err = getAnywhereCacheListResult(config, bucket) + if err != nil { + return err + } + + // Check if the cache list is now empty + if len(cacheList) == 0 { + return nil + } + + return fmt.Errorf("error while deleting the cache!") +} + func resourceDataplexLabelDiffSuppress(k, old, new string, d *schema.ResourceData) bool { if strings.HasPrefix(k, resourceDataplexGoogleProvidedLabelPrefix) && new == "" { return true @@ -992,8 +1084,13 @@ func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error break } - if len(res.Items) == 0 { - break // 0 items, bucket empty + cacheList, cacheListErr := getAnywhereCacheListResult(config, bucket) + if cacheListErr != nil { + return cacheListErr + } + + if len(res.Items) == 0 && len(cacheList) == 0 { + break // 0 items and no caches, bucket empty } if d.Get("retention_policy.0.is_locked").(bool) { @@ -1011,10 +1108,11 @@ func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error } if !d.Get("force_destroy").(bool) { - deleteErr := fmt.Errorf("Error trying to delete bucket %s containing objects without `force_destroy` set to true", bucket) + deleteErr := fmt.Errorf("Error trying to delete bucket %s without `force_destroy` set to true", bucket) log.Printf("Error! %s : %s\n\n", bucket, deleteErr) return deleteErr } + // GCS requires that a bucket be empty (have no objects or object // versions) before it can be deleted. log.Printf("[DEBUG] GCS Bucket attempting to forceDestroy\n\n") @@ -1046,6 +1144,12 @@ func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error }) } + // While we wait our objects to be deleted in background, delete the cache if any! + err = deleteAnywhereCacheIfAny(config, bucket) + if err != nil { + return fmt.Errorf("Error deleting the caches on the bucket %s : %v", bucket, err) + } + // Wait for everything to finish. wp.StopWait() } From 0b82aa7cadb0da1af19ee5b0c1d3cbabf944fbcd Mon Sep 17 00:00:00 2001 From: Margubur Rahman Date: Fri, 4 Apr 2025 02:49:25 +0530 Subject: [PATCH 2/4] updated tests to run the added workflow --- .../storage/resource_storage_anywhere_cache_test.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go b/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go index b800e04908e6..61b3df0f9e6d 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go +++ b/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go @@ -53,20 +53,15 @@ func TestAccStorageAnywhereCache_update(t *testing.T) { func testAccStorageAnywhereCache_full(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_storage_bucket" "bucket" { - name = "tf-test-bucket-name%{random_suffix}" - location = "US" -} - -resource "time_sleep" "destroy_wait_5000_seconds" { - depends_on = [google_storage_bucket.bucket] - destroy_duration = "5000s" + name = "tf-test-bucket-name%{random_suffix}" + location = "US" + force_destroy = "true" } resource "google_storage_anywhere_cache" "cache" { bucket = google_storage_bucket.bucket.name zone = "us-central1-f" ttl = "3601s" - depends_on = [time_sleep.destroy_wait_5000_seconds] } `, context) } From b876feb2bf04deb9c886a7dfe9e9d38e1bfd1b74 Mon Sep 17 00:00:00 2001 From: Margubur Rahman Date: Fri, 4 Apr 2025 04:18:25 +0530 Subject: [PATCH 3/4] addressed comments --- .../storage/resource_storage_bucket.go.tmpl | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl index a98d5401824e..b0e25802893c 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl +++ b/mmv1/third_party/terraform/services/storage/resource_storage_bucket.go.tmpl @@ -101,7 +101,7 @@ func ResourceStorageBucket() *schema.Resource { Type: schema.TypeBool, Optional: true, Default: false, - Description: `When deleting a bucket, this boolean option will delete all contained objects, or anywhereCaches (if any). If you try to delete a bucket that contains objects or anywhereCaches, Terraform will fail that run.`, + Description: `When deleting a bucket, this boolean option will delete all contained objects, or anywhereCaches (if any). If you try to delete a bucket that contains objects or anywhereCaches, Terraform will fail that run, deleting anywhereCaches may take 80 minutes to complete.`, }, "labels": { @@ -644,7 +644,7 @@ func deleteAnywhereCacheIfAny(config *transport_tpg.Config, bucket string) error if !ok { continue // If state is not a string, skip this item } - if state != "running" && state != "paused" { + if !strings.EqualFold(state, "running") && !strings.EqualFold(state, "paused") { continue } @@ -679,7 +679,7 @@ func deleteAnywhereCacheIfAny(config *transport_tpg.Config, bucket string) error return nil } - return fmt.Errorf("error while deleting the cache!") + return fmt.Errorf("Error while deleting the cache: caches still exists post 80mins of their disable time") } func resourceDataplexLabelDiffSuppress(k, old, new string, d *schema.ResourceData) bool { @@ -1129,6 +1129,13 @@ func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error // is it scheduled to be plumbed to individual providers. wp := workerpool.New(runtime.NumCPU() - 1) + wp.Submit(func() { + err = deleteAnywhereCacheIfAny(config, bucket) + if err != nil { + deleteObjectError = fmt.Errorf("error deleting the caches on the bucket %s : %w", bucket, err) + } + }) + for _, object := range res.Items { log.Printf("[DEBUG] Found %s", object.Name) object := object @@ -1144,12 +1151,6 @@ func resourceStorageBucketDelete(d *schema.ResourceData, meta interface{}) error }) } - // While we wait our objects to be deleted in background, delete the cache if any! - err = deleteAnywhereCacheIfAny(config, bucket) - if err != nil { - return fmt.Errorf("Error deleting the caches on the bucket %s : %v", bucket, err) - } - // Wait for everything to finish. wp.StopWait() } From 4595fa678e38edfea8f7be9f2d92a1cb0c9c0cda Mon Sep 17 00:00:00 2001 From: Margubur Rahman Date: Fri, 4 Apr 2025 05:01:11 +0530 Subject: [PATCH 4/4] updated the tests --- .../storage/resource_storage_anywhere_cache_test.go | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go b/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go index 61b3df0f9e6d..1d4893021bb7 100644 --- a/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go +++ b/mmv1/third_party/terraform/services/storage/resource_storage_anywhere_cache_test.go @@ -69,13 +69,9 @@ resource "google_storage_anywhere_cache" "cache" { func testAccStorageAnywhereCache_update(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_storage_bucket" "bucket" { - name = "tf-test-bucket-name%{random_suffix}" - location = "US" -} - -resource "time_sleep" "destroy_wait_5000_seconds" { - depends_on = [google_storage_bucket.bucket] - destroy_duration = "5000s" + name = "tf-test-bucket-name%{random_suffix}" + location = "US" + force_destroy = "true" } resource "google_storage_anywhere_cache" "cache" { @@ -83,7 +79,6 @@ resource "google_storage_anywhere_cache" "cache" { zone = "us-central1-f" admission_policy = "admit-on-second-miss" ttl = "3620s" - depends_on = [time_sleep.destroy_wait_5000_seconds] } `, context) }