Skip to content

Commit 836832f

Browse files
Add the vertex endpoint resource. (#6661) (#12858)
* Add the vertex endpoint resource. * Add actions block excluding update for vertex resources that do not use operations for update. * Fix declared but not used compile error * Remove pre_update from vertex entity type and feature. * Add a handwritten test that includes an update. * Make endpoint name user-specified. * Skip the vertex endpoint test. * Make vertex endpoint name field required, url param only, and immutable. * Unskip test, add link to cloud console to deployedModels description, and describe format of name field. Signed-off-by: Modular Magician <[email protected]> Signed-off-by: Modular Magician <[email protected]>
1 parent 0f155ac commit 836832f

9 files changed

+1557
-46
lines changed

.changelog/6661.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-resource
2+
google_vertex_ai_endpoint
3+
```

google/provider.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -910,9 +910,9 @@ func Provider() *schema.Provider {
910910
return provider
911911
}
912912

913-
// Generated resources: 242
913+
// Generated resources: 243
914914
// Generated IAM resources: 147
915-
// Total generated resources: 389
915+
// Total generated resources: 390
916916
func ResourceMap() map[string]*schema.Resource {
917917
resourceMap, _ := ResourceMapWithErrors()
918918
return resourceMap
@@ -1296,6 +1296,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) {
12961296
"google_tags_tag_binding": resourceTagsTagBinding(),
12971297
"google_tpu_node": resourceTPUNode(),
12981298
"google_vertex_ai_dataset": resourceVertexAIDataset(),
1299+
"google_vertex_ai_endpoint": resourceVertexAIEndpoint(),
12991300
"google_vertex_ai_featurestore": resourceVertexAIFeaturestore(),
13001301
"google_vertex_ai_featurestore_entitytype": resourceVertexAIFeaturestoreEntitytype(),
13011302
"google_vertex_ai_featurestore_entitytype_feature": resourceVertexAIFeaturestoreEntitytypeFeature(),

google/resource_vertex_ai_dataset.go

-8
Original file line numberDiff line numberDiff line change
@@ -323,14 +323,6 @@ func resourceVertexAIDatasetUpdate(d *schema.ResourceData, meta interface{}) err
323323
log.Printf("[DEBUG] Finished updating Dataset %q: %#v", d.Id(), res)
324324
}
325325

326-
err = vertexAIOperationWaitTime(
327-
config, res, project, "Updating Dataset", userAgent,
328-
d.Timeout(schema.TimeoutUpdate))
329-
330-
if err != nil {
331-
return err
332-
}
333-
334326
return resourceVertexAIDatasetRead(d, meta)
335327
}
336328

google/resource_vertex_ai_endpoint.go

+966
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// ----------------------------------------------------------------------------
2+
//
3+
// *** AUTO GENERATED CODE *** Type: MMv1 ***
4+
//
5+
// ----------------------------------------------------------------------------
6+
//
7+
// This file is automatically generated by Magic Modules and manual
8+
// changes will be clobbered when the file is regenerated.
9+
//
10+
// Please read more about how to change this file in
11+
// .github/CONTRIBUTING.md.
12+
//
13+
// ----------------------------------------------------------------------------
14+
15+
package google
16+
17+
import (
18+
"context"
19+
"log"
20+
"strings"
21+
"testing"
22+
23+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
24+
)
25+
26+
func init() {
27+
resource.AddTestSweepers("VertexAIEndpoint", &resource.Sweeper{
28+
Name: "VertexAIEndpoint",
29+
F: testSweepVertexAIEndpoint,
30+
})
31+
}
32+
33+
// At the time of writing, the CI only passes us-central1 as the region
34+
func testSweepVertexAIEndpoint(region string) error {
35+
resourceName := "VertexAIEndpoint"
36+
log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName)
37+
38+
config, err := sharedConfigForRegion(region)
39+
if err != nil {
40+
log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err)
41+
return err
42+
}
43+
44+
err = config.LoadAndValidate(context.Background())
45+
if err != nil {
46+
log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err)
47+
return err
48+
}
49+
50+
t := &testing.T{}
51+
billingId := getTestBillingAccountFromEnv(t)
52+
53+
// Setup variables to replace in list template
54+
d := &ResourceDataMock{
55+
FieldsInSchema: map[string]interface{}{
56+
"project": config.Project,
57+
"region": region,
58+
"location": region,
59+
"zone": "-",
60+
"billing_account": billingId,
61+
},
62+
}
63+
64+
listTemplate := strings.Split("https://{{region}}-aiplatform.googleapis.com/v1/projects/{{project}}/locations/{{location}}/endpoints", "?")[0]
65+
listUrl, err := replaceVars(d, config, listTemplate)
66+
if err != nil {
67+
log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err)
68+
return nil
69+
}
70+
71+
res, err := sendRequest(config, "GET", config.Project, listUrl, config.userAgent, nil)
72+
if err != nil {
73+
log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err)
74+
return nil
75+
}
76+
77+
resourceList, ok := res["endpoints"]
78+
if !ok {
79+
log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.")
80+
return nil
81+
}
82+
83+
rl := resourceList.([]interface{})
84+
85+
log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName)
86+
// Keep count of items that aren't sweepable for logging.
87+
nonPrefixCount := 0
88+
for _, ri := range rl {
89+
obj := ri.(map[string]interface{})
90+
if obj["name"] == nil {
91+
log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName)
92+
return nil
93+
}
94+
95+
name := GetResourceNameFromSelfLink(obj["name"].(string))
96+
// Skip resources that shouldn't be sweeped
97+
if !isSweepableTestResource(name) {
98+
nonPrefixCount++
99+
continue
100+
}
101+
102+
deleteTemplate := "https://{{region}}-aiplatform.googleapis.com/v1/projects/{{project}}/locations/{{location}}/endpoints/{{name}}"
103+
deleteUrl, err := replaceVars(d, config, deleteTemplate)
104+
if err != nil {
105+
log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err)
106+
return nil
107+
}
108+
deleteUrl = deleteUrl + name
109+
110+
// Don't wait on operations as we may have a lot to delete
111+
_, err = sendRequest(config, "DELETE", config.Project, deleteUrl, config.userAgent, nil)
112+
if err != nil {
113+
log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err)
114+
} else {
115+
log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name)
116+
}
117+
}
118+
119+
if nonPrefixCount > 0 {
120+
log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount)
121+
}
122+
123+
return nil
124+
}
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
// ----------------------------------------------------------------------------
2+
//
3+
// *** AUTO GENERATED CODE *** Type: MMv1 ***
4+
//
5+
// ----------------------------------------------------------------------------
6+
//
7+
// This file is automatically generated by Magic Modules and manual
8+
// changes will be clobbered when the file is regenerated.
9+
//
10+
// Please read more about how to change this file in
11+
// .github/CONTRIBUTING.md.
12+
//
13+
// ----------------------------------------------------------------------------
14+
15+
package google
16+
17+
import (
18+
"fmt"
19+
"strings"
20+
"testing"
21+
22+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
23+
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
24+
)
25+
26+
func TestAccVertexAIEndpoint_vertexAiEndpointNetwork(t *testing.T) {
27+
t.Parallel()
28+
29+
context := map[string]interface{}{
30+
"endpoint_name": fmt.Sprint(randInt(t) % 9999999999),
31+
"kms_key_name": BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name,
32+
"network_name": BootstrapSharedTestNetwork(t, "vertex"),
33+
"random_suffix": randString(t, 10),
34+
}
35+
36+
vcrTest(t, resource.TestCase{
37+
PreCheck: func() { testAccPreCheck(t) },
38+
Providers: testAccProviders,
39+
CheckDestroy: testAccCheckVertexAIEndpointDestroyProducer(t),
40+
Steps: []resource.TestStep{
41+
{
42+
Config: testAccVertexAIEndpoint_vertexAiEndpointNetwork(context),
43+
},
44+
{
45+
ResourceName: "google_vertex_ai_endpoint.endpoint",
46+
ImportState: true,
47+
ImportStateVerify: true,
48+
ImportStateVerifyIgnore: []string{"etag", "location"},
49+
},
50+
{
51+
Config: testAccVertexAIEndpoint_vertexAiEndpointNetworkUpdate(context),
52+
},
53+
{
54+
ResourceName: "google_vertex_ai_endpoint.endpoint",
55+
ImportState: true,
56+
ImportStateVerify: true,
57+
ImportStateVerifyIgnore: []string{"etag", "location"},
58+
},
59+
},
60+
})
61+
}
62+
63+
func testAccVertexAIEndpoint_vertexAiEndpointNetwork(context map[string]interface{}) string {
64+
return Nprintf(`
65+
resource "google_vertex_ai_endpoint" "endpoint" {
66+
name = "%{endpoint_name}"
67+
display_name = "sample-endpoint"
68+
description = "A sample vertex endpoint"
69+
location = "us-central1"
70+
labels = {
71+
label-one = "value-one"
72+
}
73+
network = "projects/${data.google_project.project.number}/global/networks/${data.google_compute_network.vertex_network.name}"
74+
encryption_spec {
75+
kms_key_name = "%{kms_key_name}"
76+
}
77+
depends_on = [
78+
google_service_networking_connection.vertex_vpc_connection
79+
]
80+
}
81+
82+
resource "google_service_networking_connection" "vertex_vpc_connection" {
83+
network = data.google_compute_network.vertex_network.id
84+
service = "servicenetworking.googleapis.com"
85+
reserved_peering_ranges = [google_compute_global_address.vertex_range.name]
86+
}
87+
88+
resource "google_compute_global_address" "vertex_range" {
89+
name = "tf-test-address-name%{random_suffix}"
90+
purpose = "VPC_PEERING"
91+
address_type = "INTERNAL"
92+
prefix_length = 24
93+
network = data.google_compute_network.vertex_network.id
94+
}
95+
96+
data "google_compute_network" "vertex_network" {
97+
name = "%{network_name}"
98+
}
99+
100+
resource "google_kms_crypto_key_iam_member" "crypto_key" {
101+
crypto_key_id = "%{kms_key_name}"
102+
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
103+
member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-aiplatform.iam.gserviceaccount.com"
104+
}
105+
106+
data "google_project" "project" {}
107+
`, context)
108+
}
109+
110+
func testAccVertexAIEndpoint_vertexAiEndpointNetworkUpdate(context map[string]interface{}) string {
111+
return Nprintf(`
112+
resource "google_vertex_ai_endpoint" "endpoint" {
113+
name = "%{endpoint_name}"
114+
display_name = "new-sample-endpoint"
115+
description = "An updated sample vertex endpoint"
116+
location = "us-central1"
117+
labels = {
118+
label-two = "value-two"
119+
}
120+
network = "projects/${data.google_project.project.number}/global/networks/${data.google_compute_network.vertex_network.name}"
121+
encryption_spec {
122+
kms_key_name = "%{kms_key_name}"
123+
}
124+
depends_on = [
125+
google_service_networking_connection.vertex_vpc_connection
126+
]
127+
}
128+
129+
resource "google_service_networking_connection" "vertex_vpc_connection" {
130+
network = data.google_compute_network.vertex_network.id
131+
service = "servicenetworking.googleapis.com"
132+
reserved_peering_ranges = [google_compute_global_address.vertex_range.name]
133+
}
134+
135+
resource "google_compute_global_address" "vertex_range" {
136+
name = "tf-test-address-name%{random_suffix}"
137+
purpose = "VPC_PEERING"
138+
address_type = "INTERNAL"
139+
prefix_length = 24
140+
network = data.google_compute_network.vertex_network.id
141+
}
142+
143+
data "google_compute_network" "vertex_network" {
144+
name = "%{network_name}"
145+
}
146+
147+
resource "google_kms_crypto_key_iam_member" "crypto_key" {
148+
crypto_key_id = "%{kms_key_name}"
149+
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
150+
member = "serviceAccount:service-${data.google_project.project.number}@gcp-sa-aiplatform.iam.gserviceaccount.com"
151+
}
152+
153+
data "google_project" "project" {}
154+
`, context)
155+
}
156+
157+
func testAccCheckVertexAIEndpointDestroyProducer(t *testing.T) func(s *terraform.State) error {
158+
return func(s *terraform.State) error {
159+
for name, rs := range s.RootModule().Resources {
160+
if rs.Type != "google_vertex_ai_endpoint" {
161+
continue
162+
}
163+
if strings.HasPrefix(name, "data.") {
164+
continue
165+
}
166+
167+
config := googleProviderConfig(t)
168+
169+
url, err := replaceVarsForTest(config, rs, "{{VertexAIBasePath}}projects/{{project}}/locations/{{location}}/endpoints/{{name}}")
170+
if err != nil {
171+
return err
172+
}
173+
174+
billingProject := ""
175+
176+
if config.BillingProject != "" {
177+
billingProject = config.BillingProject
178+
}
179+
180+
_, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil)
181+
if err == nil {
182+
return fmt.Errorf("VertexAIEndpoint still exists at %s", url)
183+
}
184+
}
185+
186+
return nil
187+
}
188+
}

google/resource_vertex_ai_featurestore_entitytype.go

-18
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,6 @@ func resourceVertexAIFeaturestoreEntitytypeRead(d *schema.ResourceData, meta int
231231
}
232232

233233
func resourceVertexAIFeaturestoreEntitytypeUpdate(d *schema.ResourceData, meta interface{}) error {
234-
var project string
235234
config := meta.(*Config)
236235
userAgent, err := generateUserAgentString(d, config.userAgent)
237236
if err != nil {
@@ -275,15 +274,6 @@ func resourceVertexAIFeaturestoreEntitytypeUpdate(d *schema.ResourceData, meta i
275274
if err != nil {
276275
return err
277276
}
278-
if v, ok := d.GetOk("featurestore"); ok {
279-
re := regexp.MustCompile("projects/([a-zA-Z0-9-]*)/(?:locations|regions)/([a-zA-Z0-9-]*)")
280-
switch {
281-
case re.MatchString(v.(string)):
282-
if res := re.FindStringSubmatch(v.(string)); len(res) == 3 && res[1] != "" {
283-
project = res[1]
284-
}
285-
}
286-
}
287277

288278
// err == nil indicates that the billing_project value was found
289279
if bp, err := getBillingProject(d, config); err == nil {
@@ -298,14 +288,6 @@ func resourceVertexAIFeaturestoreEntitytypeUpdate(d *schema.ResourceData, meta i
298288
log.Printf("[DEBUG] Finished updating FeaturestoreEntitytype %q: %#v", d.Id(), res)
299289
}
300290

301-
err = vertexAIOperationWaitTime(
302-
config, res, project, "Updating FeaturestoreEntitytype", userAgent,
303-
d.Timeout(schema.TimeoutUpdate))
304-
305-
if err != nil {
306-
return err
307-
}
308-
309291
return resourceVertexAIFeaturestoreEntitytypeRead(d, meta)
310292
}
311293

0 commit comments

Comments
 (0)