From 7333ffa001dafa117f63ea5ab4e4554f24e10dc4 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Thu, 20 Oct 2022 20:22:17 -0700 Subject: [PATCH 01/20] feat: add google_vertex_ai_index for Vertex AI Matching Engine --- mmv1/products/vertexai/api.yaml | 165 ++++++++++++++++++ mmv1/products/vertexai/terraform.yaml | 15 ++ .../terraform/examples/vertex_ai_index.tf.erb | 40 +++++ 3 files changed, 220 insertions(+) create mode 100644 mmv1/templates/terraform/examples/vertex_ai_index.tf.erb diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index c1ca03cad874..13fbd8725398 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -433,4 +433,169 @@ objects: description: | The disk utilization of the MetadataStore in bytes. output: true +# Vertex AI Matching Engine Index + - !ruby/object:Api::Resource + name: Index + base_url: projects/{{project}}/locations/{{region}}/indexes + create_url: projects/{{project}}/locations/{{region}}/indexes + self_link: projects/{{project}}/locations/{{region}}/indexes/{{name}} + update_verb: :PATCH + create_verb: :POST + references: !ruby/object:Api::Resource::ReferenceLinks + api: https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexes/ + async: !ruby/object:Api::OpAsync + operation: !ruby/object:Api::OpAsync::Operation + path: 'name' + base_url: '{{op_id}}' + wait_ms: 1000 + result: !ruby/object:Api::OpAsync::Result + path: 'response' + resource_inside_response: true + status: !ruby/object:Api::OpAsync::Status + path: 'done' + complete: True + allowed: + - True + - False + error: !ruby/object:Api::OpAsync::Error + path: 'error' + message: 'message' + description: |- + A representation of a collection of database items organized in a way that allows for approximate nearest neighbor (a.k.a ANN) algorithms search. + parameters: + - !ruby/object:Api::Type::String + name: region + description: The region of the Metadata Store. eg us-central1 + url_param_only: true + input: true + properties: + - !ruby/object:Api::Type::String + name: 'name' + description: The resource name of the Index. + input: true + required: true + - !ruby/object:Api::Type::String + name: 'displayName' + description: The display name of the Index. The name can be up to 128 characters long and can be consist of any UTF-8 characters. + required: true + - !ruby/object:Api::Type::String + name: 'description' + description: The description of the Index. + # Please take a look at the following link for the original definition: + # https://cloud.google.com/vertex-ai/docs/matching-engine/create-manage-index#create_index-drest + - !ruby/object:Api::Type::NestedObject + name: "metadata" + description: An additional information about the Index + properties: + - !ruby/object:Api::Type::String + name: 'contentsDeltaUri' + description: |- + Allows inserting, updating or deleting the contents of the Matching Engine Index. + The string must be a valid Cloud Storage directory path. If this + field is set when calling IndexService.UpdateIndex, then no other + Index field can be also updated as part of the same call. + The expected structure and format of the files this URI points to is + described at https://cloud.google.com/vertex-ai/docs/matching-engine/using-matching-engine#input-data-format + - !ruby/object:Api::Type::Boolean + name: 'isCompleteOverwrite' + description: |- + If this field is set together with contentsDeltaUri when calling IndexService.UpdateIndex, + then existing content of the Index will be replaced by the data from the contentsDeltaUri. + default_value: false + - !ruby/object:Api::Type::NestedObject + name: 'config' + description: The configuration of the Matching Engine Index. + properties: + - !ruby/object:Api::Type::Integer + name: 'dimensions' + description: The number of dimensions of the input vectors. + required: true + - !ruby/object:Api::Type::Integer + name: 'approximateNeighborsCount' + description: |- + The default number of neighbors to find via approximate search before exact reordering is + performed. Exact reordering is a procedure where results returned by an + approximate search algorithm are reordered via a more expensive distance computation. + Required if tree-AH algorithm is used. + - !ruby/object:Api::Type::String + name: 'distanceMeasureType' + description: |- + The distance measure used in nearest neighbor search. The value must be one of the followings: + * SQUARED_L2_DISTANCE: Euclidean (L_2) Distance + * L1_DISTANCE: Manhattan (L_1) Distance + * COSINE_DISTANCE: Cosine Distance. Defined as 1 - cosine similarity. + * DOT_PRODUCT_DISTANCE: Dot Product Distance. Defined as a negative of the dot product + default_value: "DOT_PRODUCT_DISTANCE" + - !ruby/object:Api::Type::String + name: 'featureNormType' + description: |- + Type of normalization to be carried out on each vector. The value must be one of the followings: + * UNIT_L2_NORM: Unit L2 normalization type + * NONE: No normalization type is specified. + default_value: "NONE" + - !ruby/object:Api::Type::NestedObject + name: 'algorithmConfig' + description: The configuration with regard to the algorithms used for efficient search. + properties: + - !ruby/object:Api::Type::NestedObject + name: 'treeAhConfig' + exactly_one_of: + - treeAhConfig + - bruteForceConfig + description: |- + Configuration options for using the tree-AH algorithm (Shallow tree + Asymmetric Hashing). + Please refer to this paper for more details: https://arxiv.org/abs/1908.10396 + properties: + - !ruby/object:Api::Type::Integer + name: 'leafNodeEmbeddingCount' + description: Number of embeddings on each leaf node. The default value is 1000 if not set. + default_value: 1000 + - !ruby/object:Api::Type::Integer + name: 'leafNodesToSearchPercent' + description: |- + The default percentage of leaf nodes that any query may be searched. Must be in + range 1-100, inclusive. The default value is 10 (means 10%) if not set. + default_value: 10 + - !ruby/object:Api::Type::String + name: 'bruteForceConfig' + exactly_one_of: + - treeAhConfig + - bruteForceConfig + description: |- + Configuration options for using brute force search, which simply implements the + standard linear search in the database for each query. + - !ruby/object:Api::Type::String + name: 'metadataSchemaUri' + description: |- + Immutable. Points to a YAML file stored on Google Cloud Storage describing additional information about the Index, that is specific to it. Unset if the Index does not have any additional information. + output: true + - !ruby/object:Api::Type::Array + name: 'deployedIndexes' + output: true + description: The pointers to DeployedIndexes created from this Index. An Index can be only deleted if all its DeployedIndexes had been undeployed first. + item_type: !ruby/object:Api::Type::NestedObject + properties: + - !ruby/object:Api::Type::String + name: 'indexEndpoint' + output: true + description: Immutable. A resource name of the IndexEndpoint. + - !ruby/object:Api::Type::String + name: 'deployedIndexId' + output: true + description: Immutable. The ID of the DeployedIndex in the above IndexEndpoint. + - !ruby/object:Api::Type::String + name: 'etag' + description: Used to perform consistent read-modify-write updates. + output: true + - !ruby/object:Api::Type::KeyValuePairs + name: 'labels' + description: The labels with user-defined metadata to organize your Indexes. + - !ruby/object:Api::Type::String + name: 'createTime' + output: true + description: The timestamp of when the Index was created in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. + - !ruby/object:Api::Type::String + name: 'updateTime' + output: true + description: The timestamp of when the Index was last updated in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index 2fb8639cada6..f57cd57aeae4 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -62,6 +62,21 @@ overrides: !ruby/object:Overrides::ResourceOverrides name: 'force_destroy' description: 'If set to true, any EntityTypes and Features for this Featurestore will also be deleted' default_value: false + Index: !ruby/object:Overrides::Terraform::ResourceOverride + autogen_async: false + examples: + - !ruby/object:Provider::Terraform::Examples + name: "vertex_ai_index" + primary_resource_id: "index" + vars: + name: "terraform" + display_name: "test-index" + bucket_name: "vertex-ai-index-test" + test_env_vars: + project: :PROJECT_NAME + properties: + name: !ruby/object:Overrides::Terraform::PropertyOverride + custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb FeaturestoreEntitytype: !ruby/object:Overrides::Terraform::ResourceOverride import_format: ["{{%featurestore}}/entityTypes/{{name}}"] autogen_async: false diff --git a/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb new file mode 100644 index 000000000000..6c69e083f3d0 --- /dev/null +++ b/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb @@ -0,0 +1,40 @@ +resource "google_storage_bucket" "bucket" { + name = "<%= ctx[:test_env_vars]['project'] %>-<%= ctx[:vars]['bucket_name'] %>" # Every bucket name must be globally unique + location = "us-central1" + uniform_bucket_level_access = true +} + +# The sample data comes from the following link: +# https://cloud.google.com/vertex-ai/docs/matching-engine/filtering#specify-namespaces-tokens +resource "google_storage_bucket_object" "data" { + name = "contents/data.json" + bucket = google_storage_bucket.bucket.name + content = < Date: Thu, 20 Oct 2022 22:38:31 -0700 Subject: [PATCH 02/20] fix: increase timeouts to 60 min because 20 wasn't enough for creation --- mmv1/products/vertexai/terraform.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index f57cd57aeae4..fcf77bc881e2 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -64,6 +64,10 @@ overrides: !ruby/object:Overrides::ResourceOverrides default_value: false Index: !ruby/object:Overrides::Terraform::ResourceOverride autogen_async: false + timeouts: !ruby/object:Api::Timeouts + insert_minutes: 60 + update_minutes: 60 + delete_minutes: 60 examples: - !ruby/object:Provider::Terraform::Examples name: "vertex_ai_index" From efd9549aab6c12f8bc7daf9b1ee59af6bb5a01d1 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Fri, 21 Oct 2022 20:54:03 -0700 Subject: [PATCH 03/20] fix: change coe to make name computed instead of an input --- mmv1/products/vertexai/api.yaml | 9 +++++---- mmv1/products/vertexai/terraform.yaml | 1 - mmv1/templates/terraform/examples/vertex_ai_index.tf.erb | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 13fbd8725398..f3ea6f95efb5 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -471,9 +471,8 @@ objects: properties: - !ruby/object:Api::Type::String name: 'name' - description: The resource name of the Index. - input: true - required: true + description: The resource name of the Index. This value is set by Google. + output: true - !ruby/object:Api::Type::String name: 'displayName' description: The display name of the Index. The name can be up to 128 characters long and can be consist of any UTF-8 characters. @@ -481,14 +480,16 @@ objects: - !ruby/object:Api::Type::String name: 'description' description: The description of the Index. - # Please take a look at the following link for the original definition: + # Please take a look at the following links for the original definition: # https://cloud.google.com/vertex-ai/docs/matching-engine/create-manage-index#create_index-drest + # https://cloud.google.com/vertex-ai/docs/matching-engine/configuring-indexes - !ruby/object:Api::Type::NestedObject name: "metadata" description: An additional information about the Index properties: - !ruby/object:Api::Type::String name: 'contentsDeltaUri' + input: true description: |- Allows inserting, updating or deleting the contents of the Matching Engine Index. The string must be a valid Cloud Storage directory path. If this diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index fcf77bc881e2..b05ee19a6b31 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -73,7 +73,6 @@ overrides: !ruby/object:Overrides::ResourceOverrides name: "vertex_ai_index" primary_resource_id: "index" vars: - name: "terraform" display_name: "test-index" bucket_name: "vertex-ai-index-test" test_env_vars: diff --git a/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb index 6c69e083f3d0..266e8dd5985b 100644 --- a/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb +++ b/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb @@ -16,7 +16,6 @@ EOF } resource "google_vertex_ai_index" "index" { - name = "<%= ctx[:vars]['name'] %>" labels = { foo = "bar" } From b9ba3f65d7c2bb0fade1925c6a5d6721951f8334 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Sat, 22 Oct 2022 13:23:24 -0700 Subject: [PATCH 04/20] fix: use costom flatten code to ignore_read a nested property's field --- mmv1/products/vertexai/api.yaml | 2 +- mmv1/products/vertexai/terraform.yaml | 2 ++ ...x_ai_index_ignore_contents_delta_uri.go.erb | 18 ++++++++++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index f3ea6f95efb5..44499bf52398 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -484,7 +484,7 @@ objects: # https://cloud.google.com/vertex-ai/docs/matching-engine/create-manage-index#create_index-drest # https://cloud.google.com/vertex-ai/docs/matching-engine/configuring-indexes - !ruby/object:Api::Type::NestedObject - name: "metadata" + name: 'metadata' description: An additional information about the Index properties: - !ruby/object:Api::Type::String diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index b05ee19a6b31..790ea7b5936e 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -80,6 +80,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides properties: name: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb + metadata.contentsDeltaUri: !ruby/object:Overrides::Terraform::PropertyOverride + custom_flatten: templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb FeaturestoreEntitytype: !ruby/object:Overrides::Terraform::ResourceOverride import_format: ["{{%featurestore}}/entityTypes/{{name}}"] autogen_async: false diff --git a/mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb b/mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb new file mode 100644 index 000000000000..8a98b397ffee --- /dev/null +++ b/mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb @@ -0,0 +1,18 @@ +<%# The license inside this block applies to this file. + # Copyright 2022 Google Inc. + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-%> +func flatten<%= prefix -%><%= titlelize_property(property) -%>(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // We want to ignore read on this field, but cannot because it is nested + return d.Get("metadata.0.contents_delta_uri") +} From 36ac11af55688a8050cad138cd64e8783c7b9b74 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Tue, 25 Oct 2022 08:31:25 -0700 Subject: [PATCH 05/20] fix: add skip_import_test: true to the auto-gen test --- mmv1/products/vertexai/terraform.yaml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index 790ea7b5936e..ab0115698222 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -77,9 +77,14 @@ overrides: !ruby/object:Overrides::ResourceOverrides bucket_name: "vertex-ai-index-test" test_env_vars: project: :PROJECT_NAME + # Skip the import test in an autogenerated until projects.locations.endpoints.get + # responds metadata.contents_delta_uri. Instead, add a manually written test. + skip_import_test: true properties: name: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb + etag: !ruby/object:Overrides::Terraform::PropertyOverride + ignore_read: true metadata.contentsDeltaUri: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb FeaturestoreEntitytype: !ruby/object:Overrides::Terraform::ResourceOverride From b2d7df207457c473665863e0bf84bb96f5c5f9e1 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Tue, 25 Oct 2022 08:32:51 -0700 Subject: [PATCH 06/20] feat: add a test with a manually updated ImportStateVerifyIgnore --- .../tests/resource_vertex_ai_index_test.go | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go new file mode 100644 index 000000000000..35f6d497e8a8 --- /dev/null +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go @@ -0,0 +1,113 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccVertexAIIndex_basic(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": getTestProjectFromEnv(), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVertexAIIndexDestroyProducer_basic(t), + Steps: []resource.TestStep{ + { + Config: testAccVertexAIIndex_basic(context), + }, + { + ResourceName: "google_vertex_ai_index.index", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri"}, + }, + }, + }) +} + +func testAccVertexAIIndex_basic(context map[string]interface{}) string { + return Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "%{project}-tf-test-vertex-ai-index-test%{random_suffix}" # Every bucket name must be globally unique + location = "us-central1" + uniform_bucket_level_access = true +} + +# The sample data comes from the following link: +# https://cloud.google.com/vertex-ai/docs/matching-engine/filtering#specify-namespaces-tokens +resource "google_storage_bucket_object" "data" { + name = "contents/data.json" + bucket = google_storage_bucket.bucket.name + content = < Date: Thu, 10 Nov 2022 09:35:01 -0800 Subject: [PATCH 07/20] Apply suggestions from code review [ci skip] Update descriptions based on the suggestions Co-authored-by: Stephen Lewis (Burrows) --- mmv1/products/vertexai/api.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 44499bf52398..6d35a3c61957 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -471,16 +471,16 @@ objects: properties: - !ruby/object:Api::Type::String name: 'name' - description: The resource name of the Index. This value is set by Google. + description: The resource name of the Index. output: true - !ruby/object:Api::Type::String name: 'displayName' - description: The display name of the Index. The name can be up to 128 characters long and can be consist of any UTF-8 characters. + description: The display name of the Index. The name can be up to 128 characters long and can consist of any UTF-8 characters. required: true - !ruby/object:Api::Type::String name: 'description' description: The description of the Index. - # Please take a look at the following links for the original definition: + # Please take a look at the following links for the original definition: # https://cloud.google.com/vertex-ai/docs/matching-engine/create-manage-index#create_index-drest # https://cloud.google.com/vertex-ai/docs/matching-engine/configuring-indexes - !ruby/object:Api::Type::NestedObject @@ -568,7 +568,7 @@ objects: - !ruby/object:Api::Type::String name: 'metadataSchemaUri' description: |- - Immutable. Points to a YAML file stored on Google Cloud Storage describing additional information about the Index, that is specific to it. Unset if the Index does not have any additional information. + Points to a YAML file stored on Google Cloud Storage describing additional information about the Index, that is specific to it. Unset if the Index does not have any additional information. output: true - !ruby/object:Api::Type::Array name: 'deployedIndexes' @@ -579,11 +579,11 @@ objects: - !ruby/object:Api::Type::String name: 'indexEndpoint' output: true - description: Immutable. A resource name of the IndexEndpoint. + description: A resource name of the IndexEndpoint. - !ruby/object:Api::Type::String name: 'deployedIndexId' output: true - description: Immutable. The ID of the DeployedIndex in the above IndexEndpoint. + description: The ID of the DeployedIndex in the above IndexEndpoint. - !ruby/object:Api::Type::String name: 'etag' description: Used to perform consistent read-modify-write updates. From dae899c350a549cfcd48dc414b4ac5684beba65d Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Sat, 12 Nov 2022 22:46:52 -0800 Subject: [PATCH 08/20] refactor: use ignore_read_extra instead of a manual test --- mmv1/products/vertexai/terraform.yaml | 5 +- .../tests/resource_vertex_ai_index_test.go | 113 ------------------ 2 files changed, 2 insertions(+), 116 deletions(-) delete mode 100644 mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index ab0115698222..3796824aac93 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -77,9 +77,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides bucket_name: "vertex-ai-index-test" test_env_vars: project: :PROJECT_NAME - # Skip the import test in an autogenerated until projects.locations.endpoints.get - # responds metadata.contents_delta_uri. Instead, add a manually written test. - skip_import_test: true + ignore_read_extra: + - metadata.0.contents_delta_uri properties: name: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go deleted file mode 100644 index 35f6d497e8a8..000000000000 --- a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go +++ /dev/null @@ -1,113 +0,0 @@ -package google - -import ( - "fmt" - "strings" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func TestAccVertexAIIndex_basic(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "project": getTestProjectFromEnv(), - "random_suffix": randString(t, 10), - } - - vcrTest(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckVertexAIIndexDestroyProducer_basic(t), - Steps: []resource.TestStep{ - { - Config: testAccVertexAIIndex_basic(context), - }, - { - ResourceName: "google_vertex_ai_index.index", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri"}, - }, - }, - }) -} - -func testAccVertexAIIndex_basic(context map[string]interface{}) string { - return Nprintf(` -resource "google_storage_bucket" "bucket" { - name = "%{project}-tf-test-vertex-ai-index-test%{random_suffix}" # Every bucket name must be globally unique - location = "us-central1" - uniform_bucket_level_access = true -} - -# The sample data comes from the following link: -# https://cloud.google.com/vertex-ai/docs/matching-engine/filtering#specify-namespaces-tokens -resource "google_storage_bucket_object" "data" { - name = "contents/data.json" - bucket = google_storage_bucket.bucket.name - content = < Date: Sun, 13 Nov 2022 06:08:09 -0800 Subject: [PATCH 09/20] fix: use an empty object for bruteForceConfig --- mmv1/products/vertexai/api.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 6d35a3c61957..ebdb9a0a03a8 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -557,8 +557,11 @@ objects: The default percentage of leaf nodes that any query may be searched. Must be in range 1-100, inclusive. The default value is 10 (means 10%) if not set. default_value: 10 - - !ruby/object:Api::Type::String + - !ruby/object:Api::Type::NestedObject name: 'bruteForceConfig' + allow_empty_object: true + send_empty_value: true + properties: [] exactly_one_of: - treeAhConfig - bruteForceConfig From 60f02bbb478301b99b9d87650bae0e34050e2c7a Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Sun, 13 Nov 2022 06:12:17 -0800 Subject: [PATCH 10/20] feat: define additional fields to api.yaml --- mmv1/products/vertexai/api.yaml | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index ebdb9a0a03a8..9a4c206c9502 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -602,4 +602,23 @@ objects: name: 'updateTime' output: true description: The timestamp of when the Index was last updated in RFC3339 UTC "Zulu" format, with nanosecond resolution and up to nine fractional digits. - + - !ruby/object:Api::Type::NestedObject + name: 'indexStats' + output: true + description: Stats of the index resource. + properties: + - !ruby/object:Api::Type::String + name: 'vectorsCount' + output: true + description: The number of vectors in the Index. + - !ruby/object:Api::Type::Integer + name: 'shardsCount' + output: true + description: The number of shards in the Index. + - !ruby/object:Api::Type::String + name: 'indexUpdateMethod' + default_value: BATCH_UPDATE + description: |- + The update method to use with this Index. The value must be the followings. If not set, BATCH_UPDATE will be used by default. + * BATCH_UPDATE: user can call indexes.patch with files on Cloud Storage of datapoints to update. + * STREAM_UPDATE: user can call indexes.upsertDatapoints/DeleteDatapoints to update the Index and the updates will be applied in corresponding DeployedIndexes in nearly real-time. From f1fbffe16347e2500e153d99403d5c8f7b2a5bea Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Sun, 13 Nov 2022 06:13:00 -0800 Subject: [PATCH 11/20] feat: add an example to increase test coverage --- mmv1/products/vertexai/terraform.yaml | 10 +++++ .../terraform/examples/vertex_ai_index.tf.erb | 1 + .../examples/vertex_ai_index_streaming.tf.erb | 37 +++++++++++++++++++ 3 files changed, 48 insertions(+) create mode 100644 mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index 3796824aac93..634cc9991d8f 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -79,6 +79,16 @@ overrides: !ruby/object:Overrides::ResourceOverrides project: :PROJECT_NAME ignore_read_extra: - metadata.0.contents_delta_uri + - !ruby/object:Provider::Terraform::Examples + name: "vertex_ai_index_streaming" + primary_resource_id: "index" + vars: + display_name: "test-index" + bucket_name: "vertex-ai-index-test" + test_env_vars: + project: :PROJECT_NAME + ignore_read_extra: + - metadata.0.contents_delta_uri properties: name: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb diff --git a/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb index 266e8dd5985b..f14341eebcf8 100644 --- a/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb +++ b/mmv1/templates/terraform/examples/vertex_ai_index.tf.erb @@ -36,4 +36,5 @@ resource "google_vertex_ai_index" "index" { } } } + index_update_method = "BATCH_UPDATE" } diff --git a/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb new file mode 100644 index 000000000000..da1b44f0b2a0 --- /dev/null +++ b/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb @@ -0,0 +1,37 @@ +resource "google_storage_bucket" "bucket" { + name = "<%= ctx[:test_env_vars]['project'] %>-<%= ctx[:vars]['bucket_name'] %>" # Every bucket name must be globally unique + location = "us-central1" + uniform_bucket_level_access = true +} + +# The sample data comes from the following link: +# https://cloud.google.com/vertex-ai/docs/matching-engine/filtering#specify-namespaces-tokens +resource "google_storage_bucket_object" "data" { + name = "contents/data.json" + bucket = google_storage_bucket.bucket.name + content = < Date: Sun, 13 Nov 2022 09:46:56 -0800 Subject: [PATCH 12/20] feat: deal with contentsDeltaUri as an updatable field --- mmv1/products/vertexai/api.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 9a4c206c9502..d1e3ead9dd8a 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -489,7 +489,6 @@ objects: properties: - !ruby/object:Api::Type::String name: 'contentsDeltaUri' - input: true description: |- Allows inserting, updating or deleting the contents of the Matching Engine Index. The string must be a valid Cloud Storage directory path. If this From 38ddf5ec3c53b4c2e67ef1d133eb198854b44ae1 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Sun, 13 Nov 2022 12:14:29 -0800 Subject: [PATCH 13/20] fix: fix the error because the cosine distance type only supports unit l2 norm type This is the error message from the endpoint: "Index with `COSINE_DISTANCE` distanceMeasureType currently only supports `UNIT_L2_NORM` featureNormType." --- .../terraform/examples/vertex_ai_index_streaming.tf.erb | 1 + 1 file changed, 1 insertion(+) diff --git a/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb index da1b44f0b2a0..bad557a997a4 100644 --- a/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb +++ b/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb @@ -28,6 +28,7 @@ resource "google_vertex_ai_index" "index" { dimensions = 2 approximate_neighbors_count = 150 distance_measure_type = "COSINE_DISTANCE" + feature_norm_type = "UNIT_L2_NORM" algorithm_config { brute_force_config {} } From d7640901e4c71741c55f894e2cd0bc98309997ed Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Sun, 13 Nov 2022 15:12:02 -0800 Subject: [PATCH 14/20] feat: remove approximate_neighbors_count from an example with brute_force_config approximate_neighbors_count is required if tree-AH algorithm is used. from https://cloud.google.com/vertex-ai/docs/matching-engine/configuring-indexes#brute-force-config --- .../terraform/examples/vertex_ai_index_streaming.tf.erb | 1 - 1 file changed, 1 deletion(-) diff --git a/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb b/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb index bad557a997a4..19ce99a66bd9 100644 --- a/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb +++ b/mmv1/templates/terraform/examples/vertex_ai_index_streaming.tf.erb @@ -26,7 +26,6 @@ resource "google_vertex_ai_index" "index" { contents_delta_uri = "gs://${google_storage_bucket.bucket.name}/contents" config { dimensions = 2 - approximate_neighbors_count = 150 distance_measure_type = "COSINE_DISTANCE" feature_norm_type = "UNIT_L2_NORM" algorithm_config { From 64f9a148cf2ba05909b64592e295611d6e255976 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Wed, 16 Nov 2022 09:16:16 -0800 Subject: [PATCH 15/20] test: add a handwritten test for patch --- .../tests/resource_vertex_ai_index_test.go | 186 ++++++++++++++++++ 1 file changed, 186 insertions(+) create mode 100644 mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go new file mode 100644 index 000000000000..a2ff1732f0da --- /dev/null +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go @@ -0,0 +1,186 @@ +package google + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccVertexAIIndex_updated(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": getTestProjectFromEnv(), + "random_suffix": randString(t, 10), + } + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckVertexAIIndexDestroyProducer_basic(t), + Steps: []resource.TestStep{ + { + Config: testAccVertexAIIndex_basic(context), + }, + { + ResourceName: "google_vertex_ai_index.index", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri"}, + }, + { + Config: testAccVertexAIIndex_updated(context), + }, + { + ResourceName: "google_vertex_ai_index.index", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri"}, + }, + }, + }) +} + +func testAccVertexAIIndex_basic(context map[string]interface{}) string { + return Nprintf(` +resource "google_storage_bucket" "bucket" { + name = "%{project}-tf-test-vertex-ai-index-test%{random_suffix}" # Every bucket name must be globally unique + location = "us-central1" + uniform_bucket_level_access = true +} + +# The sample data comes from the following link: +# https://cloud.google.com/vertex-ai/docs/matching-engine/filtering#specify-namespaces-tokens +resource "google_storage_bucket_object" "data" { + name = "contents/data.json" + bucket = google_storage_bucket.bucket.name + content = < Date: Thu, 17 Nov 2022 12:10:58 -0800 Subject: [PATCH 16/20] fix: add update_mask: true to use the mask as a url param --- mmv1/products/vertexai/api.yaml | 1 + .../tests/resource_vertex_ai_index_test.go | 22 ++----------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index d1e3ead9dd8a..10f5627b2172 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -440,6 +440,7 @@ objects: create_url: projects/{{project}}/locations/{{region}}/indexes self_link: projects/{{project}}/locations/{{region}}/indexes/{{name}} update_verb: :PATCH + update_mask: true create_verb: :POST references: !ruby/object:Api::Resource::ReferenceLinks api: https://cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.indexes/ diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go index a2ff1732f0da..f2afdc34c414 100644 --- a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go @@ -63,15 +63,6 @@ resource "google_storage_bucket_object" "data" { EOF } -resource "google_storage_bucket_object" "data-v2" { - name = "contents-v2/data.json" - bucket = google_storage_bucket.bucket.name - content = < Date: Mon, 21 Nov 2022 06:12:41 -0800 Subject: [PATCH 17/20] refactor: put 'input: true' on the fields patch couldn't update --- mmv1/products/vertexai/api.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mmv1/products/vertexai/api.yaml b/mmv1/products/vertexai/api.yaml index 10f5627b2172..4822ee9e2010 100644 --- a/mmv1/products/vertexai/api.yaml +++ b/mmv1/products/vertexai/api.yaml @@ -505,6 +505,7 @@ objects: default_value: false - !ruby/object:Api::Type::NestedObject name: 'config' + input: true description: The configuration of the Matching Engine Index. properties: - !ruby/object:Api::Type::Integer @@ -617,6 +618,7 @@ objects: description: The number of shards in the Index. - !ruby/object:Api::Type::String name: 'indexUpdateMethod' + input: true default_value: BATCH_UPDATE description: |- The update method to use with this Index. The value must be the followings. If not set, BATCH_UPDATE will be used by default. From cda37c4fcc33c1be6440c43c23ecdddacbdc015f Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Mon, 21 Nov 2022 06:35:30 -0800 Subject: [PATCH 18/20] feat: use custom pre update code for a nested object --- mmv1/products/vertexai/terraform.yaml | 4 ++++ .../pre_update/vertex_ai_index.go.erb | 22 +++++++++++++++++++ .../tests/resource_vertex_ai_index_test.go | 22 ++++++++++++++++++- 3 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 mmv1/templates/terraform/pre_update/vertex_ai_index.go.erb diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index 634cc9991d8f..a6a4fb5ea1fb 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -79,6 +79,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides project: :PROJECT_NAME ignore_read_extra: - metadata.0.contents_delta_uri + - metadata.0.is_complete_overwrite - !ruby/object:Provider::Terraform::Examples name: "vertex_ai_index_streaming" primary_resource_id: "index" @@ -89,6 +90,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides project: :PROJECT_NAME ignore_read_extra: - metadata.0.contents_delta_uri + - metadata.0.is_complete_overwrite properties: name: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/name_from_self_link.erb @@ -96,6 +98,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides ignore_read: true metadata.contentsDeltaUri: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb + custom_code: !ruby/object:Provider::Terraform::CustomCode + pre_update: templates/terraform/pre_update/vertex_ai_index.go.erb FeaturestoreEntitytype: !ruby/object:Overrides::Terraform::ResourceOverride import_format: ["{{%featurestore}}/entityTypes/{{name}}"] autogen_async: false diff --git a/mmv1/templates/terraform/pre_update/vertex_ai_index.go.erb b/mmv1/templates/terraform/pre_update/vertex_ai_index.go.erb new file mode 100644 index 000000000000..e8dc872ed5c1 --- /dev/null +++ b/mmv1/templates/terraform/pre_update/vertex_ai_index.go.erb @@ -0,0 +1,22 @@ +newUpdateMask := []string{} + +if d.HasChange("metadata.0.contents_delta_uri") { + // Use the current value of isCompleteOverwrite when updating contentsDeltaUri + newUpdateMask = append(newUpdateMask, "metadata.contentsDeltaUri") + newUpdateMask = append(newUpdateMask, "metadata.isCompleteOverwrite") +} + +for _, mask := range updateMask { + // Use granular update masks instead of 'metadata' to avoid the following error: + // 'If `contents_delta_gcs_uri` is set as part of `index.metadata`, then no other Index fields can be also updated as part of the same update call.' + if mask == "metadata" { + continue + } + newUpdateMask = append(newUpdateMask, mask) +} + +// Refreshing updateMask after adding extra schema entries +url, err = addQueryParams(url, map[string]string{"updateMask": strings.Join(newUpdateMask, ",")}) +if err != nil { + return err +} diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go index f2afdc34c414..ed201c9a8c9f 100644 --- a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go @@ -63,6 +63,15 @@ resource "google_storage_bucket_object" "data" { EOF } +resource "google_storage_bucket_object" "data-v2" { + name = "contents-v2/data.json" + bucket = google_storage_bucket.bucket.name + content = < Date: Mon, 21 Nov 2022 07:24:17 -0800 Subject: [PATCH 19/20] fix: update the handwritten test accordingly --- .../terraform/tests/resource_vertex_ai_index_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go index ed201c9a8c9f..bc4910b69e19 100644 --- a/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go +++ b/mmv1/third_party/terraform/tests/resource_vertex_ai_index_test.go @@ -29,7 +29,7 @@ func TestAccVertexAIIndex_updated(t *testing.T) { ResourceName: "google_vertex_ai_index.index", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri"}, + ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri", "metadata.0.is_complete_overwrite"}, }, { Config: testAccVertexAIIndex_updated(context), @@ -38,7 +38,7 @@ func TestAccVertexAIIndex_updated(t *testing.T) { ResourceName: "google_vertex_ai_index.index", ImportState: true, ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri"}, + ImportStateVerifyIgnore: []string{"etag", "region", "metadata.0.contents_delta_uri", "metadata.0.is_complete_overwrite"}, }, }, }) From 4661d7c0dec4e7c54548aae1ffc08c77ff1040c7 Mon Sep 17 00:00:00 2001 From: Shotaro Kohama Date: Thu, 24 Nov 2022 15:41:07 -0800 Subject: [PATCH 20/20] feat: add custom flatten code for is_complete_overwrite --- mmv1/products/vertexai/terraform.yaml | 2 ++ ...i_index_ignore_is_complete_overwrite.go.erb | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) create mode 100644 mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_is_complete_overwrite.go.erb diff --git a/mmv1/products/vertexai/terraform.yaml b/mmv1/products/vertexai/terraform.yaml index a6a4fb5ea1fb..bdcf31067750 100644 --- a/mmv1/products/vertexai/terraform.yaml +++ b/mmv1/products/vertexai/terraform.yaml @@ -98,6 +98,8 @@ overrides: !ruby/object:Overrides::ResourceOverrides ignore_read: true metadata.contentsDeltaUri: !ruby/object:Overrides::Terraform::PropertyOverride custom_flatten: templates/terraform/custom_flatten/vertex_ai_index_ignore_contents_delta_uri.go.erb + metadata.isCompleteOverwrite: !ruby/object:Overrides::Terraform::PropertyOverride + custom_flatten: templates/terraform/custom_flatten/vertex_ai_index_ignore_is_complete_overwrite.go.erb custom_code: !ruby/object:Provider::Terraform::CustomCode pre_update: templates/terraform/pre_update/vertex_ai_index.go.erb FeaturestoreEntitytype: !ruby/object:Overrides::Terraform::ResourceOverride diff --git a/mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_is_complete_overwrite.go.erb b/mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_is_complete_overwrite.go.erb new file mode 100644 index 000000000000..45c123dff438 --- /dev/null +++ b/mmv1/templates/terraform/custom_flatten/vertex_ai_index_ignore_is_complete_overwrite.go.erb @@ -0,0 +1,18 @@ +<%# The license inside this block applies to this file. + # Copyright 2022 Google Inc. + # Licensed under the Apache License, Version 2.0 (the "License"); + # you may not use this file except in compliance with the License. + # You may obtain a copy of the License at + # + # http://www.apache.org/licenses/LICENSE-2.0 + # + # Unless required by applicable law or agreed to in writing, software + # distributed under the License is distributed on an "AS IS" BASIS, + # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + # See the License for the specific language governing permissions and + # limitations under the License. +-%> +func flatten<%= prefix -%><%= titlelize_property(property) -%>(v interface{}, d *schema.ResourceData, config *Config) interface{} { + // We want to ignore read on this field, but cannot because it is nested + return d.Get("metadata.0.is_complete_overwrite") +}