Skip to content

Commit 38f7df6

Browse files
googlyrahmanNA2047
authored andcommitted
Adds google_storage_anywhere_cache resource (GoogleCloudPlatform#13023)
1 parent b4a041e commit 38f7df6

File tree

5 files changed

+298
-1
lines changed

5 files changed

+298
-1
lines changed
+99
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
# Copyright 2025 Google Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
---
15+
name: 'AnywhereCache'
16+
kind: 'storage#anywhereCache'
17+
description: |
18+
The Google Cloud Storage (GCS) Anywhere Cache feature allows users to
19+
create SSD backed zonal read cache for their buckets. These zonal
20+
caches are co-located with the customers compute engines to provide
21+
cost efficiency.
22+
base_url: 'b/{{bucket}}/anywhereCaches'
23+
self_link: 'b/{{bucket}}/anywhereCaches/{{anywhere_cache_id}}'
24+
create_url: 'b/{{bucket}}/anywhereCaches/'
25+
delete_url: 'b/{{bucket}}/anywhereCaches/{{anywhere_cache_id}}/disable'
26+
update_verb: 'PATCH'
27+
delete_verb: 'POST'
28+
import_format: ['b/{{bucket}}/anywhereCaches/{{anywhere_cache_id}}']
29+
id_format: '{{bucket}}/{{anywhere_cache_id}}'
30+
examples:
31+
- name: 'storage_anywhere_cache_basic'
32+
primary_resource_id: 'cache'
33+
vars:
34+
bucket_name: 'bucket-name'
35+
external_providers: ["time"]
36+
identity:
37+
- bucket
38+
- anywhereCacheId
39+
custom_code:
40+
post_create: templates/terraform/post_create/storage_anywhere_cache_post_create.go.tmpl
41+
autogen_async: false
42+
timeouts:
43+
insert_minutes: 240
44+
update_minutes: 240
45+
delete_minutes: 20
46+
async:
47+
type: 'OpAsync'
48+
actions:
49+
- create
50+
- update
51+
operation:
52+
full_url: 'selfLink'
53+
result:
54+
resource_inside_response: true
55+
parameters:
56+
- name: 'bucket'
57+
type: ResourceRef
58+
required: true
59+
immutable: true
60+
url_param_only: true
61+
resource: 'Bucket'
62+
imports: 'name'
63+
properties:
64+
- name: 'zone'
65+
type: String
66+
required: true
67+
immutable: true
68+
description: The zone in which the cache instance needs to be created. For example, `us-central1-a.`
69+
- name: 'admissionPolicy'
70+
type: Enum
71+
enum_values:
72+
- 'admit-on-first-miss'
73+
- 'admit-on-second-miss'
74+
default_value: 'admit-on-first-miss'
75+
description: The cache admission policy dictates whether a block should be inserted upon a cache miss.
76+
- name: 'ttl'
77+
type: String
78+
default_value: '86400s'
79+
description: The TTL of all cache entries in whole seconds. e.g., "7200s". It defaults to `86400s`
80+
- name: 'anywhereCacheId'
81+
type: String
82+
output: true
83+
description: The ID of the Anywhere cache instance.
84+
- name: 'createTime'
85+
type: Time
86+
output: true
87+
description: The creation time of the cache instance in RFC 3339 format.
88+
- name: 'updateTime'
89+
type: Time
90+
output: true
91+
description: The modification time of the cache instance metadata in RFC 3339 format.
92+
- name: 'pendingUpdate'
93+
type: Boolean
94+
output: true
95+
description: True if the cache instance has an active Update long-running operation.
96+
- name: 'state'
97+
type: String
98+
output: true
99+
description: The current state of the cache instance.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
resource "google_storage_bucket" "bucket" {
2+
name = "{{index $.Vars "bucket_name"}}"
3+
location = "US"
4+
}
5+
6+
resource "time_sleep" "destroy_wait_5000_seconds" {
7+
depends_on = [google_storage_bucket.bucket]
8+
destroy_duration = "5000s"
9+
}
10+
11+
resource "google_storage_anywhere_cache" "cache" {
12+
bucket = google_storage_bucket.bucket.name
13+
zone = "us-central1-f"
14+
ttl = "3601s"
15+
depends_on = [time_sleep.destroy_wait_5000_seconds]
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
nameVal, ok := opRes["name"].(string)
2+
if !ok {
3+
return fmt.Errorf("opRes['name'] is not a string: %v", opRes["name"])
4+
}
5+
6+
nameParts := strings.Split(nameVal, "/")
7+
if len(nameParts) != 6 || nameParts[0] != "projects" || nameParts[2] != "buckets" || nameParts[4] != "anywhereCaches" {
8+
return fmt.Errorf("error parsing the anywhereCacheId from %s", nameVal)
9+
}
10+
11+
anywhereCacheID := nameParts[5]
12+
if err := d.Set("anywhere_cache_id", anywhereCacheID); err != nil {
13+
return err
14+
}
15+
16+
// This may have caused the ID to update - update it if so.
17+
id, err = tpgresource.ReplaceVars(d, config, "{{"{{bucket}}/{{anywhere_cache_id}}"}}")
18+
if err != nil {
19+
return fmt.Errorf("Error constructing id: %s", err)
20+
}
21+
d.SetId(id)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package storage_test
4+
5+
import (
6+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
7+
"github.com/hashicorp/terraform-plugin-testing/plancheck"
8+
"github.com/hashicorp/terraform-provider-google/google/acctest"
9+
"testing"
10+
)
11+
12+
func TestAccStorageAnywhereCache_update(t *testing.T) {
13+
t.Parallel()
14+
15+
context := map[string]interface{}{
16+
"random_suffix": acctest.RandString(t, 10),
17+
}
18+
19+
acctest.VcrTest(t, resource.TestCase{
20+
PreCheck: func() { acctest.AccTestPreCheck(t) },
21+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
22+
ExternalProviders: map[string]resource.ExternalProvider{
23+
"time": {},
24+
},
25+
Steps: []resource.TestStep{
26+
{
27+
Config: testAccStorageAnywhereCache_full(context),
28+
},
29+
{
30+
ResourceName: "google_storage_anywhere_cache.cache",
31+
ImportState: true,
32+
ImportStateVerify: true,
33+
ImportStateVerifyIgnore: []string{"bucket"},
34+
},
35+
{
36+
Config: testAccStorageAnywhereCache_update(context),
37+
ConfigPlanChecks: resource.ConfigPlanChecks{
38+
PreApply: []plancheck.PlanCheck{
39+
plancheck.ExpectResourceAction("google_storage_anywhere_cache.cache", plancheck.ResourceActionUpdate),
40+
},
41+
},
42+
},
43+
{
44+
ResourceName: "google_storage_anywhere_cache.cache",
45+
ImportState: true,
46+
ImportStateVerify: true,
47+
ImportStateVerifyIgnore: []string{"bucket"},
48+
},
49+
},
50+
})
51+
}
52+
53+
func testAccStorageAnywhereCache_full(context map[string]interface{}) string {
54+
return acctest.Nprintf(`
55+
resource "google_storage_bucket" "bucket" {
56+
name = "tf-test-bucket-name%{random_suffix}"
57+
location = "US"
58+
}
59+
60+
resource "time_sleep" "destroy_wait_5000_seconds" {
61+
depends_on = [google_storage_bucket.bucket]
62+
destroy_duration = "5000s"
63+
}
64+
65+
resource "google_storage_anywhere_cache" "cache" {
66+
bucket = google_storage_bucket.bucket.name
67+
zone = "us-central1-f"
68+
ttl = "3601s"
69+
depends_on = [time_sleep.destroy_wait_5000_seconds]
70+
}
71+
`, context)
72+
}
73+
74+
func testAccStorageAnywhereCache_update(context map[string]interface{}) string {
75+
return acctest.Nprintf(`
76+
resource "google_storage_bucket" "bucket" {
77+
name = "tf-test-bucket-name%{random_suffix}"
78+
location = "US"
79+
}
80+
81+
resource "time_sleep" "destroy_wait_5000_seconds" {
82+
depends_on = [google_storage_bucket.bucket]
83+
destroy_duration = "5000s"
84+
}
85+
86+
resource "google_storage_anywhere_cache" "cache" {
87+
bucket = google_storage_bucket.bucket.name
88+
zone = "us-central1-f"
89+
admission_policy = "admit-on-second-miss"
90+
ttl = "3620s"
91+
depends_on = [time_sleep.destroy_wait_5000_seconds]
92+
}
93+
`, context)
94+
}

mmv1/third_party/terraform/services/storage/resource_storage_bucket_sweeper.go

+68-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
13
package storage
24

35
import (
@@ -13,6 +15,60 @@ func init() {
1315
sweeper.AddTestSweepers("StorageBucket", testSweepStorageBucket)
1416
}
1517

18+
func disableAnywhereCacheIfAny(config *transport_tpg.Config, bucket string) bool {
19+
// Define the cache list URL
20+
cacheListUrl := fmt.Sprintf("https://storage.googleapis.com/storage/v1/b/%s/anywhereCaches/", bucket)
21+
22+
// Send request to get resource list
23+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
24+
Config: config,
25+
Method: "GET",
26+
Project: config.Project,
27+
RawURL: cacheListUrl,
28+
UserAgent: config.UserAgent,
29+
})
30+
if err != nil {
31+
log.Printf("[INFO][SWEEPER_LOG] Error fetching caches from url %s: %s", cacheListUrl, err)
32+
return false
33+
}
34+
35+
resourceList, ok := res["items"]
36+
if !ok {
37+
log.Printf("[INFO][SWEEPER_LOG] No caches found for %s.", bucket)
38+
return true
39+
}
40+
41+
rl := resourceList.([]interface{})
42+
43+
// Iterate over each object in the resource list
44+
for _, item := range rl {
45+
// Ensure the item is a map
46+
obj := item.(map[string]interface{})
47+
48+
// Check the state of the object
49+
state := obj["state"].(string)
50+
if state != "running" && state != "paused" {
51+
continue
52+
}
53+
54+
// Disable the cache if state is running or paused
55+
disableUrl := fmt.Sprintf("https://storage.googleapis.com/storage/v1/b/%s/anywhereCaches/%s/disable", obj["bucket"], obj["anywhereCacheId"])
56+
_, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
57+
Config: config,
58+
Method: "POST",
59+
Project: config.Project,
60+
RawURL: disableUrl,
61+
UserAgent: config.UserAgent,
62+
})
63+
if err != nil {
64+
log.Printf("[INFO][SWEEPER_LOG] Error disabling cache: %s", err)
65+
}
66+
}
67+
68+
// Return true if no items were found, otherwise false
69+
return len(rl) == 0
70+
}
71+
1672
// At the time of writing, the CI only passes us-central1 as the region
1773
func testSweepStorageBucket(region string) error {
1874
resourceName := "StorageBucket"
@@ -63,6 +119,7 @@ func testSweepStorageBucket(region string) error {
63119
log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName)
64120
// Count items that weren't sweeped.
65121
nonPrefixCount := 0
122+
bucketWithCaches := 0
66123
for _, ri := range rl {
67124
obj := ri.(map[string]interface{})
68125

@@ -73,6 +130,13 @@ func testSweepStorageBucket(region string) error {
73130
continue
74131
}
75132

133+
readyToDeleteBucket := disableAnywhereCacheIfAny(config, id)
134+
if !readyToDeleteBucket {
135+
log.Printf("[INFO][SWEEPER_LOG] Bucket %s has anywhere caches, requests have been made to backend to disable them, The bucket would be automatically deleted once caches are deleted from bucket", id)
136+
bucketWithCaches++
137+
continue
138+
}
139+
76140
deleteUrl := fmt.Sprintf("https://storage.googleapis.com/storage/v1/b/%s", id)
77141
_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
78142
Config: config,
@@ -89,7 +153,10 @@ func testSweepStorageBucket(region string) error {
89153
}
90154

91155
if nonPrefixCount > 0 {
92-
log.Printf("[INFO][SWEEPER_LOG] %d items without tf-test prefix remain.", nonPrefixCount)
156+
log.Printf("[INFO][SWEEPER_LOG] %d items without valid test prefixes remain.", nonPrefixCount)
157+
}
158+
if bucketWithCaches > 0 {
159+
log.Printf("[INFO][SWEEPER_LOG] %d items with valid test prefixes remain, and can not be deleted due to their underlying resources", bucketWithCaches)
93160
}
94161

95162
return nil

0 commit comments

Comments
 (0)