diff --git a/.changelog/6565.txt b/.changelog/6565.txt new file mode 100644 index 00000000000..3a8b0440617 --- /dev/null +++ b/.changelog/6565.txt @@ -0,0 +1,5 @@ +```release-note:new-resource +`google_vertex_ai_featurestore` (ga only) +`google_vertex_ai_featurestore_entitytype` (ga only) +`google_vertex_ai_featurestore_entitytype_feature` (ga only) +``` diff --git a/google/provider.go b/google/provider.go index d4fdc1b1420..0d53d472489 100644 --- a/google/provider.go +++ b/google/provider.go @@ -900,9 +900,9 @@ func Provider() *schema.Provider { return provider } -// Generated resources: 237 +// Generated resources: 240 // Generated IAM resources: 138 -// Total generated resources: 375 +// Total generated resources: 378 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1275,6 +1275,9 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_tags_tag_binding": resourceTagsTagBinding(), "google_tpu_node": resourceTPUNode(), "google_vertex_ai_dataset": resourceVertexAIDataset(), + "google_vertex_ai_featurestore": resourceVertexAIFeaturestore(), + "google_vertex_ai_featurestore_entitytype": resourceVertexAIFeaturestoreEntitytype(), + "google_vertex_ai_featurestore_entitytype_feature": resourceVertexAIFeaturestoreEntitytypeFeature(), "google_vpc_access_connector": resourceVPCAccessConnector(), "google_workflows_workflow": resourceWorkflowsWorkflow(), }, diff --git a/google/resource_vertex_ai_featurestore.go b/google/resource_vertex_ai_featurestore.go new file mode 100644 index 00000000000..f8e03ca47c9 --- /dev/null +++ b/google/resource_vertex_ai_featurestore.go @@ -0,0 +1,544 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceVertexAIFeaturestore() *schema.Resource { + return &schema.Resource{ + Create: resourceVertexAIFeaturestoreCreate, + Read: resourceVertexAIFeaturestoreRead, + Update: resourceVertexAIFeaturestoreUpdate, + Delete: resourceVertexAIFeaturestoreDelete, + + Importer: &schema.ResourceImporter{ + State: resourceVertexAIFeaturestoreImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "encryption_spec": { + Type: schema.TypeList, + Optional: true, + Description: `If set, both of the online and offline data storage will be secured by this key.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "kms_key_name": { + Type: schema.TypeString, + Required: true, + Description: `The Cloud KMS resource identifier of the customer managed encryption key used to protect a resource. Has the form: projects/my-project/locations/my-region/keyRings/my-kr/cryptoKeys/my-key. The key needs to be in the same region as where the compute resource is created.`, + }, + }, + }, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `A set of key/value label pairs to assign to this Featurestore.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The name of the Featurestore. This value may be up to 60 characters, and valid characters are [a-z0-9_]. The first character cannot be a number.`, + }, + "online_serving_config": { + Type: schema.TypeList, + Optional: true, + Description: `Config for online serving resources.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "fixed_node_count": { + Type: schema.TypeInt, + Required: true, + Description: `The number of nodes for each cluster. The number of nodes will not scale automatically but can be scaled manually by providing different values when updating.`, + }, + }, + }, + }, + "region": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: true, + Description: `The region of the dataset. eg us-central1`, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `The timestamp of when the featurestore was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.`, + }, + "etag": { + Type: schema.TypeString, + Computed: true, + Description: `Used to perform consistent read-modify-write updates.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The timestamp of when the featurestore was last updated in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.`, + }, + "force_destroy": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceVertexAIFeaturestoreCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + labelsProp, err := expandVertexAIFeaturestoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + onlineServingConfigProp, err := expandVertexAIFeaturestoreOnlineServingConfig(d.Get("online_serving_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("online_serving_config"); !isEmptyValue(reflect.ValueOf(onlineServingConfigProp)) && (ok || !reflect.DeepEqual(v, onlineServingConfigProp)) { + obj["onlineServingConfig"] = onlineServingConfigProp + } + encryptionSpecProp, err := expandVertexAIFeaturestoreEncryptionSpec(d.Get("encryption_spec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("encryption_spec"); !isEmptyValue(reflect.ValueOf(encryptionSpecProp)) && (ok || !reflect.DeepEqual(v, encryptionSpecProp)) { + obj["encryptionSpec"] = encryptionSpecProp + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}projects/{{project}}/locations/{{region}}/featurestores?featurestoreId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new Featurestore: %#v", obj) + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Featurestore: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating Featurestore: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{region}}/featurestores/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = vertexAIOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating Featurestore", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create Featurestore: %s", err) + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "projects/{{project}}/locations/{{region}}/featurestores/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating Featurestore %q: %#v", d.Id(), res) + + return resourceVertexAIFeaturestoreRead(d, meta) +} + +func resourceVertexAIFeaturestoreRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}projects/{{project}}/locations/{{region}}/featurestores/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Featurestore: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("VertexAIFeaturestore %q", d.Id())) + } + + // Explicitly set virtual fields to default values if unset + if _, ok := d.GetOkExists("force_destroy"); !ok { + if err := d.Set("force_destroy", false); err != nil { + return fmt.Errorf("Error setting force_destroy: %s", err) + } + } + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading Featurestore: %s", err) + } + + if err := d.Set("create_time", flattenVertexAIFeaturestoreCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Featurestore: %s", err) + } + if err := d.Set("update_time", flattenVertexAIFeaturestoreUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading Featurestore: %s", err) + } + if err := d.Set("labels", flattenVertexAIFeaturestoreLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading Featurestore: %s", err) + } + if err := d.Set("online_serving_config", flattenVertexAIFeaturestoreOnlineServingConfig(res["onlineServingConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading Featurestore: %s", err) + } + if err := d.Set("encryption_spec", flattenVertexAIFeaturestoreEncryptionSpec(res["encryptionSpec"], d, config)); err != nil { + return fmt.Errorf("Error reading Featurestore: %s", err) + } + + return nil +} + +func resourceVertexAIFeaturestoreUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Featurestore: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + labelsProp, err := expandVertexAIFeaturestoreLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + onlineServingConfigProp, err := expandVertexAIFeaturestoreOnlineServingConfig(d.Get("online_serving_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("online_serving_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, onlineServingConfigProp)) { + obj["onlineServingConfig"] = onlineServingConfigProp + } + encryptionSpecProp, err := expandVertexAIFeaturestoreEncryptionSpec(d.Get("encryption_spec"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("encryption_spec"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, encryptionSpecProp)) { + obj["encryptionSpec"] = encryptionSpecProp + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}projects/{{project}}/locations/{{region}}/featurestores/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating Featurestore %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("online_serving_config") { + updateMask = append(updateMask, "onlineServingConfig") + } + + if d.HasChange("encryption_spec") { + updateMask = append(updateMask, "encryptionSpec") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating Featurestore %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating Featurestore %q: %#v", d.Id(), res) + } + + err = vertexAIOperationWaitTime( + config, res, project, "Updating Featurestore", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceVertexAIFeaturestoreRead(d, meta) +} + +func resourceVertexAIFeaturestoreDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := getProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for Featurestore: %s", err) + } + billingProject = project + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}projects/{{project}}/locations/{{region}}/featurestores/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + + if v, ok := d.GetOk("force_destroy"); ok { + url, err = addQueryParams(url, map[string]string{"force": fmt.Sprintf("%v", v)}) + if err != nil { + return err + } + } + log.Printf("[DEBUG] Deleting Featurestore %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "Featurestore") + } + + err = vertexAIOperationWaitTime( + config, res, project, "Deleting Featurestore", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting Featurestore %q: %#v", d.Id(), res) + return nil +} + +func resourceVertexAIFeaturestoreImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "projects/(?P[^/]+)/locations/(?P[^/]+)/featurestores/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + "(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "projects/{{project}}/locations/{{region}}/featurestores/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Explicitly set virtual fields to default values on import + if err := d.Set("force_destroy", false); err != nil { + return nil, fmt.Errorf("Error setting force_destroy: %s", err) + } + + return []*schema.ResourceData{d}, nil +} + +func flattenVertexAIFeaturestoreCreateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreUpdateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreOnlineServingConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["fixed_node_count"] = + flattenVertexAIFeaturestoreOnlineServingConfigFixedNodeCount(original["fixedNodeCount"], d, config) + return []interface{}{transformed} +} +func flattenVertexAIFeaturestoreOnlineServingConfigFixedNodeCount(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // Handles the string fixed64 format + if strVal, ok := v.(string); ok { + if intVal, err := stringToFixed64(strVal); err == nil { + return intVal + } + } + + // number values are represented as float64 + if floatVal, ok := v.(float64); ok { + intVal := int(floatVal) + return intVal + } + + return v // let terraform core handle it otherwise +} + +func flattenVertexAIFeaturestoreEncryptionSpec(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["kms_key_name"] = + flattenVertexAIFeaturestoreEncryptionSpecKmsKeyName(original["kmsKeyName"], d, config) + return []interface{}{transformed} +} +func flattenVertexAIFeaturestoreEncryptionSpecKmsKeyName(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandVertexAIFeaturestoreLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandVertexAIFeaturestoreOnlineServingConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedFixedNodeCount, err := expandVertexAIFeaturestoreOnlineServingConfigFixedNodeCount(original["fixed_node_count"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFixedNodeCount); val.IsValid() && !isEmptyValue(val) { + transformed["fixedNodeCount"] = transformedFixedNodeCount + } + + return transformed, nil +} + +func expandVertexAIFeaturestoreOnlineServingConfigFixedNodeCount(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandVertexAIFeaturestoreEncryptionSpec(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedKmsKeyName, err := expandVertexAIFeaturestoreEncryptionSpecKmsKeyName(original["kms_key_name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedKmsKeyName); val.IsValid() && !isEmptyValue(val) { + transformed["kmsKeyName"] = transformedKmsKeyName + } + + return transformed, nil +} + +func expandVertexAIFeaturestoreEncryptionSpecKmsKeyName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_vertex_ai_featurestore_entitytype.go b/google/resource_vertex_ai_featurestore_entitytype.go new file mode 100644 index 00000000000..af05a43bac5 --- /dev/null +++ b/google/resource_vertex_ai_featurestore_entitytype.go @@ -0,0 +1,472 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "regexp" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceVertexAIFeaturestoreEntitytype() *schema.Resource { + return &schema.Resource{ + Create: resourceVertexAIFeaturestoreEntitytypeCreate, + Read: resourceVertexAIFeaturestoreEntitytypeRead, + Update: resourceVertexAIFeaturestoreEntitytypeUpdate, + Delete: resourceVertexAIFeaturestoreEntitytypeDelete, + + Importer: &schema.ResourceImporter{ + State: resourceVertexAIFeaturestoreEntitytypeImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "featurestore": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the Featurestore to use, in the format projects/{project}/locations/{location}/featurestores/{featurestore}.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `A set of key/value label pairs to assign to this EntityType.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "monitoring_config": { + Type: schema.TypeList, + Optional: true, + Description: `The default monitoring configuration for all Features under this EntityType. + +If this is populated with [FeaturestoreMonitoringConfig.monitoring_interval] specified, snapshot analysis monitoring is enabled. Otherwise, snapshot analysis monitoring is disabled.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "snapshot_analysis": { + Type: schema.TypeList, + Optional: true, + Description: `Configuration of how features in Featurestore are monitored.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "disabled": { + Type: schema.TypeBool, + Optional: true, + Description: `The monitoring schedule for snapshot analysis. For EntityType-level config: unset / disabled = true indicates disabled by default for Features under it; otherwise by default enable snapshot analysis monitoring with monitoringInterval for Features under it.`, + Default: false, + }, + }, + }, + }, + }, + }, + }, + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The name of the EntityType. This value may be up to 60 characters, and valid characters are [a-z0-9_]. The first character cannot be a number.`, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `The timestamp of when the featurestore was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.`, + }, + "etag": { + Type: schema.TypeString, + Computed: true, + Description: `Used to perform consistent read-modify-write updates.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The timestamp of when the featurestore was last updated in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceVertexAIFeaturestoreEntitytypeCreate(d *schema.ResourceData, meta interface{}) error { + var project string + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + labelsProp, err := expandVertexAIFeaturestoreEntitytypeLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + monitoringConfigProp, err := expandVertexAIFeaturestoreEntitytypeMonitoringConfig(d.Get("monitoring_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("monitoring_config"); !isEmptyValue(reflect.ValueOf(monitoringConfigProp)) && (ok || !reflect.DeepEqual(v, monitoringConfigProp)) { + obj["monitoringConfig"] = monitoringConfigProp + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{featurestore}}/entityTypes?entityTypeId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new FeaturestoreEntitytype: %#v", obj) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + if v, ok := d.GetOk("featurestore"); ok { + re := regexp.MustCompile("projects/([a-zA-Z0-9-]*)/(?:locations|regions)/([a-zA-Z0-9-]*)") + switch { + case re.MatchString(v.(string)): + if res := re.FindStringSubmatch(v.(string)); len(res) == 3 && res[1] != "" { + project = res[1] + } + } + } + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating FeaturestoreEntitytype: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{featurestore}}/entityTypes/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = vertexAIOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating FeaturestoreEntitytype", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create FeaturestoreEntitytype: %s", err) + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "{{featurestore}}/entityTypes/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating FeaturestoreEntitytype %q: %#v", d.Id(), res) + + return resourceVertexAIFeaturestoreEntitytypeRead(d, meta) +} + +func resourceVertexAIFeaturestoreEntitytypeRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{featurestore}}/entityTypes/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("VertexAIFeaturestoreEntitytype %q", d.Id())) + } + + if err := d.Set("create_time", flattenVertexAIFeaturestoreEntitytypeCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytype: %s", err) + } + if err := d.Set("update_time", flattenVertexAIFeaturestoreEntitytypeUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytype: %s", err) + } + if err := d.Set("labels", flattenVertexAIFeaturestoreEntitytypeLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytype: %s", err) + } + if err := d.Set("monitoring_config", flattenVertexAIFeaturestoreEntitytypeMonitoringConfig(res["monitoringConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytype: %s", err) + } + + return nil +} + +func resourceVertexAIFeaturestoreEntitytypeUpdate(d *schema.ResourceData, meta interface{}) error { + var project string + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + obj := make(map[string]interface{}) + labelsProp, err := expandVertexAIFeaturestoreEntitytypeLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + monitoringConfigProp, err := expandVertexAIFeaturestoreEntitytypeMonitoringConfig(d.Get("monitoring_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("monitoring_config"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, monitoringConfigProp)) { + obj["monitoringConfig"] = monitoringConfigProp + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{featurestore}}/entityTypes/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating FeaturestoreEntitytype %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("monitoring_config") { + updateMask = append(updateMask, "monitoringConfig") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + if v, ok := d.GetOk("featurestore"); ok { + re := regexp.MustCompile("projects/([a-zA-Z0-9-]*)/(?:locations|regions)/([a-zA-Z0-9-]*)") + switch { + case re.MatchString(v.(string)): + if res := re.FindStringSubmatch(v.(string)); len(res) == 3 && res[1] != "" { + project = res[1] + } + } + } + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating FeaturestoreEntitytype %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating FeaturestoreEntitytype %q: %#v", d.Id(), res) + } + + err = vertexAIOperationWaitTime( + config, res, project, "Updating FeaturestoreEntitytype", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceVertexAIFeaturestoreEntitytypeRead(d, meta) +} + +func resourceVertexAIFeaturestoreEntitytypeDelete(d *schema.ResourceData, meta interface{}) error { + var project string + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{featurestore}}/entityTypes/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + if v, ok := d.GetOk("featurestore"); ok { + re := regexp.MustCompile("projects/([a-zA-Z0-9-]*)/(?:locations|regions)/([a-zA-Z0-9-]*)") + switch { + case re.MatchString(v.(string)): + if res := re.FindStringSubmatch(v.(string)); len(res) == 3 && res[1] != "" { + project = res[1] + } + } + } + log.Printf("[DEBUG] Deleting FeaturestoreEntitytype %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "FeaturestoreEntitytype") + } + + err = vertexAIOperationWaitTime( + config, res, project, "Deleting FeaturestoreEntitytype", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting FeaturestoreEntitytype %q: %#v", d.Id(), res) + return nil +} + +func resourceVertexAIFeaturestoreEntitytypeImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "(?P.+)/entityTypes/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "{{featurestore}}/entityTypes/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenVertexAIFeaturestoreEntitytypeCreateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreEntitytypeUpdateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreEntitytypeLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreEntitytypeMonitoringConfig(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["snapshot_analysis"] = + flattenVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysis(original["snapshotAnalysis"], d, config) + return []interface{}{transformed} +} +func flattenVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysis(v interface{}, d *schema.ResourceData, config *Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["disabled"] = + flattenVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysisDisabled(original["disabled"], d, config) + return []interface{}{transformed} +} +func flattenVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysisDisabled(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandVertexAIFeaturestoreEntitytypeLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandVertexAIFeaturestoreEntitytypeMonitoringConfig(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedSnapshotAnalysis, err := expandVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysis(original["snapshot_analysis"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSnapshotAnalysis); val.IsValid() && !isEmptyValue(val) { + transformed["snapshotAnalysis"] = transformedSnapshotAnalysis + } + + return transformed, nil +} + +func expandVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysis(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedDisabled, err := expandVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysisDisabled(original["disabled"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDisabled); val.IsValid() && !isEmptyValue(val) { + transformed["disabled"] = transformedDisabled + } + + return transformed, nil +} + +func expandVertexAIFeaturestoreEntitytypeMonitoringConfigSnapshotAnalysisDisabled(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_vertex_ai_featurestore_entitytype_feature.go b/google/resource_vertex_ai_featurestore_entitytype_feature.go new file mode 100644 index 00000000000..edc02b27b18 --- /dev/null +++ b/google/resource_vertex_ai_featurestore_entitytype_feature.go @@ -0,0 +1,417 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "log" + "reflect" + "regexp" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceVertexAIFeaturestoreEntitytypeFeature() *schema.Resource { + return &schema.Resource{ + Create: resourceVertexAIFeaturestoreEntitytypeFeatureCreate, + Read: resourceVertexAIFeaturestoreEntitytypeFeatureRead, + Update: resourceVertexAIFeaturestoreEntitytypeFeatureUpdate, + Delete: resourceVertexAIFeaturestoreEntitytypeFeatureDelete, + + Importer: &schema.ResourceImporter{ + State: resourceVertexAIFeaturestoreEntitytypeFeatureImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + Schema: map[string]*schema.Schema{ + "entitytype": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `The name of the Featurestore to use, in the format projects/{project}/locations/{location}/featurestores/{featurestore}/entityTypes/{entitytype}.`, + }, + "value_type": { + Type: schema.TypeString, + Required: true, + Description: `Type of Feature value. Immutable. https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.featurestores.entityTypes.features#ValueType`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Description of the feature.`, + }, + "labels": { + Type: schema.TypeMap, + Optional: true, + Description: `A set of key/value label pairs to assign to the feature.`, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + "name": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Description: `The name of the feature. The feature can be up to 64 characters long and can consist only of ASCII Latin letters A-Z and a-z, underscore(_), and ASCII digits 0-9 starting with a letter. The value will be unique given an entity type.`, + }, + "create_time": { + Type: schema.TypeString, + Computed: true, + Description: `The timestamp of when the entity type was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.`, + }, + "etag": { + Type: schema.TypeString, + Computed: true, + Description: `Used to perform consistent read-modify-write updates.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The timestamp when the entity type was most recently updated in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits.`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceVertexAIFeaturestoreEntitytypeFeatureCreate(d *schema.ResourceData, meta interface{}) error { + var project string + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + labelsProp, err := expandVertexAIFeaturestoreEntitytypeFeatureLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(labelsProp)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + descriptionProp, err := expandVertexAIFeaturestoreEntitytypeFeatureDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + valueTypeProp, err := expandVertexAIFeaturestoreEntitytypeFeatureValueType(d.Get("value_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("value_type"); !isEmptyValue(reflect.ValueOf(valueTypeProp)) && (ok || !reflect.DeepEqual(v, valueTypeProp)) { + obj["valueType"] = valueTypeProp + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{entitytype}}/features?featureId={{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new FeaturestoreEntitytypeFeature: %#v", obj) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + if v, ok := d.GetOk("entitytype"); ok { + re := regexp.MustCompile("projects/([a-zA-Z0-9-]*)/(?:locations|regions)/([a-zA-Z0-9-]*)") + switch { + case re.MatchString(v.(string)): + if res := re.FindStringSubmatch(v.(string)); len(res) == 3 && res[1] != "" { + project = res[1] + } + } + } + res, err := sendRequestWithTimeout(config, "POST", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutCreate)) + if err != nil { + return fmt.Errorf("Error creating FeaturestoreEntitytypeFeature: %s", err) + } + + // Store the ID now + id, err := replaceVars(d, config, "{{entitytype}}/features/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + // Use the resource in the operation response to populate + // identity fields and d.Id() before read + var opRes map[string]interface{} + err = vertexAIOperationWaitTimeWithResponse( + config, res, &opRes, project, "Creating FeaturestoreEntitytypeFeature", userAgent, + d.Timeout(schema.TimeoutCreate)) + if err != nil { + // The resource didn't actually create + d.SetId("") + + return fmt.Errorf("Error waiting to create FeaturestoreEntitytypeFeature: %s", err) + } + + // This may have caused the ID to update - update it if so. + id, err = replaceVars(d, config, "{{entitytype}}/features/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating FeaturestoreEntitytypeFeature %q: %#v", d.Id(), res) + + return resourceVertexAIFeaturestoreEntitytypeFeatureRead(d, meta) +} + +func resourceVertexAIFeaturestoreEntitytypeFeatureRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{entitytype}}/features/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequest(config, "GET", billingProject, url, userAgent, nil) + if err != nil { + return handleNotFoundError(err, d, fmt.Sprintf("VertexAIFeaturestoreEntitytypeFeature %q", d.Id())) + } + + if err := d.Set("create_time", flattenVertexAIFeaturestoreEntitytypeFeatureCreateTime(res["createTime"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytypeFeature: %s", err) + } + if err := d.Set("update_time", flattenVertexAIFeaturestoreEntitytypeFeatureUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytypeFeature: %s", err) + } + if err := d.Set("labels", flattenVertexAIFeaturestoreEntitytypeFeatureLabels(res["labels"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytypeFeature: %s", err) + } + if err := d.Set("description", flattenVertexAIFeaturestoreEntitytypeFeatureDescription(res["description"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytypeFeature: %s", err) + } + if err := d.Set("value_type", flattenVertexAIFeaturestoreEntitytypeFeatureValueType(res["valueType"], d, config)); err != nil { + return fmt.Errorf("Error reading FeaturestoreEntitytypeFeature: %s", err) + } + + return nil +} + +func resourceVertexAIFeaturestoreEntitytypeFeatureUpdate(d *schema.ResourceData, meta interface{}) error { + var project string + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + obj := make(map[string]interface{}) + labelsProp, err := expandVertexAIFeaturestoreEntitytypeFeatureLabels(d.Get("labels"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("labels"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, labelsProp)) { + obj["labels"] = labelsProp + } + descriptionProp, err := expandVertexAIFeaturestoreEntitytypeFeatureDescription(d.Get("description"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + valueTypeProp, err := expandVertexAIFeaturestoreEntitytypeFeatureValueType(d.Get("value_type"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("value_type"); !isEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, valueTypeProp)) { + obj["valueType"] = valueTypeProp + } + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{entitytype}}/features/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating FeaturestoreEntitytypeFeature %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("labels") { + updateMask = append(updateMask, "labels") + } + + if d.HasChange("description") { + updateMask = append(updateMask, "description") + } + + if d.HasChange("value_type") { + updateMask = append(updateMask, "valueType") + } + // updateMask is a URL parameter but not present in the schema, so replaceVars + // won't set it + url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) + if err != nil { + return err + } + if v, ok := d.GetOk("entitytype"); ok { + re := regexp.MustCompile("projects/([a-zA-Z0-9-]*)/(?:locations|regions)/([a-zA-Z0-9-]*)") + switch { + case re.MatchString(v.(string)): + if res := re.FindStringSubmatch(v.(string)); len(res) == 3 && res[1] != "" { + project = res[1] + } + } + } + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "PATCH", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return fmt.Errorf("Error updating FeaturestoreEntitytypeFeature %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating FeaturestoreEntitytypeFeature %q: %#v", d.Id(), res) + } + + err = vertexAIOperationWaitTime( + config, res, project, "Updating FeaturestoreEntitytypeFeature", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceVertexAIFeaturestoreEntitytypeFeatureRead(d, meta) +} + +func resourceVertexAIFeaturestoreEntitytypeFeatureDelete(d *schema.ResourceData, meta interface{}) error { + var project string + config := meta.(*Config) + userAgent, err := generateUserAgentString(d, config.userAgent) + if err != nil { + return err + } + + billingProject := "" + + url, err := replaceVars(d, config, "{{VertexAIBasePath}}{{entitytype}}/features/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + if v, ok := d.GetOk("entitytype"); ok { + re := regexp.MustCompile("projects/([a-zA-Z0-9-]*)/(?:locations|regions)/([a-zA-Z0-9-]*)") + switch { + case re.MatchString(v.(string)): + if res := re.FindStringSubmatch(v.(string)); len(res) == 3 && res[1] != "" { + project = res[1] + } + } + } + log.Printf("[DEBUG] Deleting FeaturestoreEntitytypeFeature %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := getBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := sendRequestWithTimeout(config, "DELETE", billingProject, url, userAgent, obj, d.Timeout(schema.TimeoutDelete)) + if err != nil { + return handleNotFoundError(err, d, "FeaturestoreEntitytypeFeature") + } + + err = vertexAIOperationWaitTime( + config, res, project, "Deleting FeaturestoreEntitytypeFeature", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting FeaturestoreEntitytypeFeature %q: %#v", d.Id(), res) + return nil +} + +func resourceVertexAIFeaturestoreEntitytypeFeatureImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*Config) + if err := parseImportId([]string{ + "(?P.+)/features/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := replaceVars(d, config, "{{entitytype}}/features/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenVertexAIFeaturestoreEntitytypeFeatureCreateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreEntitytypeFeatureUpdateTime(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreEntitytypeFeatureLabels(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreEntitytypeFeatureDescription(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func flattenVertexAIFeaturestoreEntitytypeFeatureValueType(v interface{}, d *schema.ResourceData, config *Config) interface{} { + return v +} + +func expandVertexAIFeaturestoreEntitytypeFeatureLabels(v interface{}, d TerraformResourceData, config *Config) (map[string]string, error) { + if v == nil { + return map[string]string{}, nil + } + m := make(map[string]string) + for k, val := range v.(map[string]interface{}) { + m[k] = val.(string) + } + return m, nil +} + +func expandVertexAIFeaturestoreEntitytypeFeatureDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandVertexAIFeaturestoreEntitytypeFeatureValueType(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/google/resource_vertex_ai_featurestore_entitytype_feature_generated_test.go b/google/resource_vertex_ai_featurestore_entitytype_feature_generated_test.go new file mode 100644 index 00000000000..2db3ae58ed1 --- /dev/null +++ b/google/resource_vertex_ai_featurestore_entitytype_feature_generated_test.go @@ -0,0 +1,115 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccVertexAIFeaturestoreEntitytypeFeature_vertexAiFeaturestoreEntitytypeFeatureExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVertexAIFeaturestoreEntitytypeFeatureDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccVertexAIFeaturestoreEntitytypeFeature_vertexAiFeaturestoreEntitytypeFeatureExample(context), + }, + { + ResourceName: "google_vertex_ai_featurestore_entitytype_feature.feature", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "etag", "entitytype"}, + }, + }, + }) +} + +func testAccVertexAIFeaturestoreEntitytypeFeature_vertexAiFeaturestoreEntitytypeFeatureExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_vertex_ai_featurestore" "featurestore" { + name = "terraform%{random_suffix}" + labels = { + foo = "bar" + } + region = "us-central1" + online_serving_config { + fixed_node_count = 2 + } +} + +resource "google_vertex_ai_featurestore_entitytype" "entity" { + name = "terraform%{random_suffix}" + labels = { + foo = "bar" + } + featurestore = google_vertex_ai_featurestore.featurestore.id +} + +resource "google_vertex_ai_featurestore_entitytype_feature" "feature" { + name = "terraform%{random_suffix}" + labels = { + foo = "bar" + } + entitytype = google_vertex_ai_featurestore_entitytype.entity.id + + value_type = "INT64_ARRAY" +} +`, context) +} + +func testAccCheckVertexAIFeaturestoreEntitytypeFeatureDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_vertex_ai_featurestore_entitytype_feature" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{VertexAIBasePath}}{{entitytype}}/features/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil) + if err == nil { + return fmt.Errorf("VertexAIFeaturestoreEntitytypeFeature still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_vertex_ai_featurestore_entitytype_generated_test.go b/google/resource_vertex_ai_featurestore_entitytype_generated_test.go new file mode 100644 index 00000000000..5c8b4ff129e --- /dev/null +++ b/google/resource_vertex_ai_featurestore_entitytype_generated_test.go @@ -0,0 +1,111 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccVertexAIFeaturestoreEntitytype_vertexAiFeaturestoreEntitytypeExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgFromEnv(t), + "billing_account": getTestBillingAccountFromEnv(t), + "kms_key_name": BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name, + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVertexAIFeaturestoreEntitytypeDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccVertexAIFeaturestoreEntitytype_vertexAiFeaturestoreEntitytypeExample(context), + }, + { + ResourceName: "google_vertex_ai_featurestore_entitytype.entity", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "etag", "featurestore"}, + }, + }, + }) +} + +func testAccVertexAIFeaturestoreEntitytype_vertexAiFeaturestoreEntitytypeExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_vertex_ai_featurestore" "featurestore" { + name = "terraform%{random_suffix}" + labels = { + foo = "bar" + } + region = "us-central1" + online_serving_config { + fixed_node_count = 2 + } + encryption_spec { + kms_key_name = "%{kms_key_name}" + } +} + +resource "google_vertex_ai_featurestore_entitytype" "entity" { + name = "terraform%{random_suffix}" + labels = { + foo = "bar" + } + featurestore = google_vertex_ai_featurestore.featurestore.id +} +`, context) +} + +func testAccCheckVertexAIFeaturestoreEntitytypeDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_vertex_ai_featurestore_entitytype" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{VertexAIBasePath}}{{featurestore}}/entityTypes/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil) + if err == nil { + return fmt.Errorf("VertexAIFeaturestoreEntitytype still exists at %s", url) + } + } + + return nil + } +} diff --git a/google/resource_vertex_ai_featurestore_generated_test.go b/google/resource_vertex_ai_featurestore_generated_test.go new file mode 100644 index 00000000000..a58ddd2d83a --- /dev/null +++ b/google/resource_vertex_ai_featurestore_generated_test.go @@ -0,0 +1,104 @@ +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccVertexAIFeaturestore_vertexAiFeaturestoreExample(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": getTestOrgFromEnv(t), + "billing_account": getTestBillingAccountFromEnv(t), + "kms_key_name": BootstrapKMSKeyInLocation(t, "us-central1").CryptoKey.Name, + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVertexAIFeaturestoreDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccVertexAIFeaturestore_vertexAiFeaturestoreExample(context), + }, + { + ResourceName: "google_vertex_ai_featurestore.featurestore", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"name", "etag", "region", "force_destroy"}, + }, + }, + }) +} + +func testAccVertexAIFeaturestore_vertexAiFeaturestoreExample(context map[string]interface{}) string { + return Nprintf(` +resource "google_vertex_ai_featurestore" "featurestore" { + name = "terraform%{random_suffix}" + labels = { + foo = "bar" + } + region = "us-central1" + online_serving_config { + fixed_node_count = 2 + } + encryption_spec { + kms_key_name = "%{kms_key_name}" + } + force_destroy = true +} +`, context) +} + +func testAccCheckVertexAIFeaturestoreDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + for name, rs := range s.RootModule().Resources { + if rs.Type != "google_vertex_ai_featurestore" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := googleProviderConfig(t) + + url, err := replaceVarsForTest(config, rs, "{{VertexAIBasePath}}projects/{{project}}/locations/{{region}}/featurestores/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = sendRequest(config, "GET", billingProject, url, config.userAgent, nil) + if err == nil { + return fmt.Errorf("VertexAIFeaturestore still exists at %s", url) + } + } + + return nil + } +} diff --git a/website/docs/r/vertex_ai_featurestore.html.markdown b/website/docs/r/vertex_ai_featurestore.html.markdown index 1c0abfb475a..cbdddb8aa72 100644 --- a/website/docs/r/vertex_ai_featurestore.html.markdown +++ b/website/docs/r/vertex_ai_featurestore.html.markdown @@ -22,12 +22,10 @@ description: |- A collection of DataItems and Annotations on them. -~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. -See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. To get more information about Featurestore, see: -* [API documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.featurestores) +* [API documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.featurestores) * How-to Guides * [Official Documentation](https://cloud.google.com/vertex-ai/docs) @@ -36,7 +34,6 @@ To get more information about Featurestore, see: ```hcl resource "google_vertex_ai_featurestore" "featurestore" { - provider = google-beta name = "terraform" labels = { foo = "bar" diff --git a/website/docs/r/vertex_ai_featurestore_entitytype.html.markdown b/website/docs/r/vertex_ai_featurestore_entitytype.html.markdown index a3d159f658b..4c1e00c8af6 100644 --- a/website/docs/r/vertex_ai_featurestore_entitytype.html.markdown +++ b/website/docs/r/vertex_ai_featurestore_entitytype.html.markdown @@ -22,12 +22,10 @@ description: |- An entity type is a type of object in a system that needs to be modeled and have stored information about. For example, driver is an entity type, and driver0 is an instance of an entity type driver. -~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. -See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. To get more information about FeaturestoreEntitytype, see: -* [API documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.featurestores.entityTypes) +* [API documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.featurestores.entityTypes) * How-to Guides * [Official Documentation](https://cloud.google.com/vertex-ai/docs) @@ -36,7 +34,6 @@ To get more information about FeaturestoreEntitytype, see: ```hcl resource "google_vertex_ai_featurestore" "featurestore" { - provider = google-beta name = "terraform" labels = { foo = "bar" @@ -51,12 +48,39 @@ resource "google_vertex_ai_featurestore" "featurestore" { } resource "google_vertex_ai_featurestore_entitytype" "entity" { - provider = google-beta name = "terraform" labels = { foo = "bar" } featurestore = google_vertex_ai_featurestore.featurestore.id +} +``` +## Example Usage - Vertex Ai Featurestore Entitytype With Beta Fields + + +```hcl +resource "google_vertex_ai_featurestore" "featurestore" { + provider = google-beta + name = "terraform2" + labels = { + foo = "bar" + } + region = "us-central1" + online_serving_config { + fixed_node_count = 2 + } + encryption_spec { + kms_key_name = "kms-name" + } +} + +resource "google_vertex_ai_featurestore_entitytype" "entity" { + provider = google-beta + name = "terraform2" + labels = { + foo = "bar" + } + featurestore = google_vertex_ai_featurestore.featurestore.id monitoring_config { snapshot_analysis { disabled = false @@ -109,7 +133,7 @@ The following arguments are supported: The monitoring schedule for snapshot analysis. For EntityType-level config: unset / disabled = true indicates disabled by default for Features under it; otherwise by default enable snapshot analysis monitoring with monitoringInterval for Features under it. * `monitoring_interval` - - (Optional) + (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) Configuration of the snapshot analysis based monitoring pipeline running interval. The value is rolled up to full day. A duration in seconds with up to nine fractional digits, terminated by 's'. Example: "3.5s". diff --git a/website/docs/r/vertex_ai_featurestore_entitytype_feature.html.markdown b/website/docs/r/vertex_ai_featurestore_entitytype_feature.html.markdown index 434dce39956..ae48cfef2e0 100644 --- a/website/docs/r/vertex_ai_featurestore_entitytype_feature.html.markdown +++ b/website/docs/r/vertex_ai_featurestore_entitytype_feature.html.markdown @@ -22,21 +22,23 @@ description: |- Feature Metadata information that describes an attribute of an entity type. For example, apple is an entity type, and color is a feature that describes apple. -~> **Warning:** This resource is in beta, and should be used with the terraform-provider-google-beta provider. -See [Provider Versions](https://terraform.io/docs/providers/google/guides/provider_versions.html) for more details on beta resources. To get more information about FeaturestoreEntitytypeFeature, see: -* [API documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1beta1/projects.locations.featurestores.entityTypes.features) +* [API documentation](https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.featurestores.entityTypes.features) * How-to Guides * [Official Documentation](https://cloud.google.com/vertex-ai/docs) + ## Example Usage - Vertex Ai Featurestore Entitytype Feature ```hcl resource "google_vertex_ai_featurestore" "featurestore" { - provider = google-beta name = "terraform" labels = { foo = "bar" @@ -48,12 +50,51 @@ resource "google_vertex_ai_featurestore" "featurestore" { } resource "google_vertex_ai_featurestore_entitytype" "entity" { - provider = google-beta name = "terraform" labels = { foo = "bar" } featurestore = google_vertex_ai_featurestore.featurestore.id +} + +resource "google_vertex_ai_featurestore_entitytype_feature" "feature" { + name = "terraform" + labels = { + foo = "bar" + } + entitytype = google_vertex_ai_featurestore_entitytype.entity.id + + value_type = "INT64_ARRAY" +} +``` + +## Example Usage - Vertex Ai Featurestore Entitytype Feature With Beta Fields + + +```hcl +resource "google_vertex_ai_featurestore" "featurestore" { + provider = google-beta + name = "terraform2" + labels = { + foo = "bar" + } + region = "us-central1" + online_serving_config { + fixed_node_count = 2 + } +} + +resource "google_vertex_ai_featurestore_entitytype" "entity" { + provider = google-beta + name = "terraform2" + labels = { + foo = "bar" + } + featurestore = google_vertex_ai_featurestore.featurestore.id monitoring_config { snapshot_analysis { disabled = false @@ -64,7 +105,7 @@ resource "google_vertex_ai_featurestore_entitytype" "entity" { resource "google_vertex_ai_featurestore_entitytype_feature" "feature" { provider = google-beta - name = "terraform" + name = "terraform2" labels = { foo = "bar" }