diff --git a/mmv1/products/dlp/StoredInfoType.yaml b/mmv1/products/dlp/StoredInfoType.yaml index 2f3067cfae4d..0c34e38502c2 100644 --- a/mmv1/products/dlp/StoredInfoType.yaml +++ b/mmv1/products/dlp/StoredInfoType.yaml @@ -48,6 +48,8 @@ examples: test_env_vars: project: :PROJECT_NAME custom_code: !ruby/object:Provider::Terraform::CustomCode + resource_definition: templates/terraform/resource_definition/dlp_stored_info_type.go.erb + constants: templates/terraform/constants/dlp_stored_info_type.go.erb decoder: templates/terraform/decoders/dlp_stored_info_type.go.erb encoder: templates/terraform/encoders/dlp_stored_info_type.go.erb custom_import: templates/terraform/custom_import/dlp_import.go.erb @@ -82,7 +84,6 @@ properties: - !ruby/object:Api::Type::NestedObject name: 'regex' description: Regular expression which defines the rule. - immutable: true exactly_one_of: - dictionary - regex @@ -96,14 +97,12 @@ properties: Its syntax (https://github.com/google/re2/wiki/Syntax) can be found under the google/re2 repository on GitHub. - !ruby/object:Api::Type::Array name: 'groupIndexes' - immutable: true description: | The index of the submatch to extract as findings. When not specified, the entire match is returned. No more than 3 may be included. item_type: Api::Type::Integer - !ruby/object:Api::Type::NestedObject name: 'dictionary' description: Dictionary which defines the rule. - immutable: true exactly_one_of: - dictionary - regex @@ -139,7 +138,6 @@ properties: - !ruby/object:Api::Type::NestedObject name: 'largeCustomDictionary' description: Dictionary which defines the rule. - immutable: true exactly_one_of: - dictionary - regex diff --git a/mmv1/templates/terraform/constants/dlp_stored_info_type.go.erb b/mmv1/templates/terraform/constants/dlp_stored_info_type.go.erb new file mode 100644 index 000000000000..f1536c58761b --- /dev/null +++ b/mmv1/templates/terraform/constants/dlp_stored_info_type.go.erb @@ -0,0 +1,36 @@ +<%# The license inside this block applies to this file. + # Copyright 2023 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 storedInfoTypeCustomizeDiffFunc(diff TerraformResourceDiff) error { + oldDict, newDict := diff.GetChange("dictionary") + oldRegex, newRegex := diff.GetChange("regex") + oldLargeCD, newLargeCD := diff.GetChange("large_custom_dictionary") + if !isEmptyValue(reflect.ValueOf(oldDict)) && isEmptyValue(reflect.ValueOf(newDict)) { + diff.ForceNew("dictionary") + return nil + } + if !isEmptyValue(reflect.ValueOf(oldRegex)) && isEmptyValue(reflect.ValueOf(newRegex)) { + diff.ForceNew("regex") + return nil + } + if !isEmptyValue(reflect.ValueOf(oldLargeCD)) && isEmptyValue(reflect.ValueOf(newLargeCD)) { + diff.ForceNew("large_custom_dictionary") + return nil + } + return nil +} + +func storedInfoTypeCustomizeDiff(_ context.Context, diff *schema.ResourceDiff, v interface{}) error { + return storedInfoTypeCustomizeDiffFunc(diff) +} \ No newline at end of file diff --git a/mmv1/templates/terraform/resource_definition/dlp_stored_info_type.go.erb b/mmv1/templates/terraform/resource_definition/dlp_stored_info_type.go.erb new file mode 100644 index 000000000000..bb28ca6d3e1b --- /dev/null +++ b/mmv1/templates/terraform/resource_definition/dlp_stored_info_type.go.erb @@ -0,0 +1,15 @@ +<%# The license inside this block applies to this file. + # Copyright 2023 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. +-%> +CustomizeDiff: storedInfoTypeCustomizeDiff, diff --git a/mmv1/third_party/terraform/tests/resource_data_loss_prevention_stored_info_type_test.go b/mmv1/third_party/terraform/tests/resource_data_loss_prevention_stored_info_type_test.go index 93bbec732b9b..a259151b52af 100644 --- a/mmv1/third_party/terraform/tests/resource_data_loss_prevention_stored_info_type_test.go +++ b/mmv1/third_party/terraform/tests/resource_data_loss_prevention_stored_info_type_test.go @@ -6,6 +6,209 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" ) +func TestAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeCustomDiffFuncForceNew(t *testing.T) { + t.Parallel() + + cases := map[string]struct { + before map[string]interface{} + after map[string]interface{} + forcenew bool + }{ + "updating_dictionary": { + before: map[string]interface{}{ + "dictionary": map[string]interface{}{ + "word_list": map[string]interface{}{ + "word": []string{"word", "word2"}, + }, + }, + }, + after: map[string]interface{}{ + "dictionary": map[string]interface{}{ + "word_list": map[string]interface{}{ + "word": []string{"wordnew", "word2"}, + }, + }, + }, + forcenew: false, + }, + "updating_large_custom_dictionary": { + before: map[string]interface{}{ + "large_custom_dictionary": map[string]interface{}{ + "output_path": map[string]interface{}{ + "path": "gs://sample-dlp-bucket/something.json", + }, + }, + }, + after: map[string]interface{}{ + "large_custom_dictionary": map[string]interface{}{ + "output_path": map[string]interface{}{ + "path": "gs://sample-dlp-bucket/somethingnew.json", + }, + }, + }, + forcenew: false, + }, + "updating_regex": { + before: map[string]interface{}{ + "regex": map[string]interface{}{ + "pattern": "patient", + }, + }, + after: map[string]interface{}{ + "regex": map[string]interface{}{ + "pattern": "newpatient", + }, + }, + forcenew: false, + }, + "changing_from_dictionary_to_large_custom_dictionary": { + before: map[string]interface{}{ + "dictionary": map[string]interface{}{ + "word_list": map[string]interface{}{ + "word": []string{"word", "word2"}, + }, + }, + }, + after: map[string]interface{}{ + "large_custom_dictionary": map[string]interface{}{ + "output_path": map[string]interface{}{ + "path": "gs://sample-dlp-bucket/something.json", + }, + }, + }, + forcenew: true, + }, + "changing_from_dictionary_to_regex": { + before: map[string]interface{}{ + "dictionary": map[string]interface{}{ + "word_list": map[string]interface{}{ + "word": []string{"word", "word2"}, + }, + }, + }, + after: map[string]interface{}{ + "regex": map[string]interface{}{ + "pattern": "patient", + }, + }, + forcenew: true, + }, + "changing_from_large_custom_dictionary_to_regex": { + before: map[string]interface{}{ + "large_custom_dictionary": map[string]interface{}{ + "output_path": map[string]interface{}{ + "path": "gs://sample-dlp-bucket/something.json", + }, + }, + }, + after: map[string]interface{}{ + "regex": map[string]interface{}{ + "pattern": "patient", + }, + }, + forcenew: true, + }, + "changing_from_large_custom_dictionary_to_dictionary": { + before: map[string]interface{}{ + "large_custom_dictionary": map[string]interface{}{ + "output_path": map[string]interface{}{ + "path": "gs://sample-dlp-bucket/something.json", + }, + }, + }, + after: map[string]interface{}{ + "dictionary": map[string]interface{}{ + "word_list": map[string]interface{}{ + "word": []string{"word", "word2"}, + }, + }, + }, + forcenew: true, + }, + "changing_from_regex_to_dictionary": { + before: map[string]interface{}{ + "regex": map[string]interface{}{ + "pattern": "patient", + }, + }, + after: map[string]interface{}{ + "dictionary": map[string]interface{}{ + "word_list": map[string]interface{}{ + "word": []string{"word", "word2"}, + }, + }, + }, + forcenew: true, + }, + "changing_from_regex_to_large_custom_dictionary": { + before: map[string]interface{}{ + "regex": map[string]interface{}{ + "pattern": "patient", + }, + }, + after: map[string]interface{}{ + "large_custom_dictionary": map[string]interface{}{ + "output_path": map[string]interface{}{ + "path": "gs://sample-dlp-bucket/something.json", + }, + }, + }, + forcenew: true, + }, + } + + for tn, tc := range cases { + + fieldBefore := "" + fieldAfter := "" + switch tn { + case "updating_dictionary": + fieldBefore = "dictionary" + fieldAfter = fieldBefore + case "updating_large_custom_dictionary": + fieldBefore = "large_custom_dictionary" + fieldAfter = fieldBefore + case "updating_regex": + fieldBefore = "regex" + fieldAfter = fieldBefore + case "changing_from_dictionary_to_large_custom_dictionary": + fieldBefore = "dictionary" + fieldAfter = "large_custom_dictionary" + case "changing_from_dictionary_to_regex": + fieldBefore = "dictionary" + fieldAfter = "regex" + case "changing_from_large_custom_dictionary_to_regex": + fieldBefore = "large_custom_dictionary" + fieldAfter = "regex" + case "changing_from_large_custom_dictionary_to_dictionary": + fieldBefore = "large_custom_dictionary" + fieldAfter = "dictionary" + case "changing_from_regex_to_dictionary": + fieldBefore = "regex" + fieldAfter = "dictionary" + case "changing_from_regex_to_large_custom_dictionary": + fieldBefore = "regex" + fieldAfter = "large_custom_dictionary" + } + + d := &ResourceDiffMock{ + Before: map[string]interface{}{ + fieldBefore: tc.before[fieldBefore], + }, + After: map[string]interface{}{ + fieldAfter: tc.after[fieldAfter], + }, + } + err := storedInfoTypeCustomizeDiffFunc(d) + if err != nil { + t.Errorf("failed, expected no error but received - %s for the condition %s", err, tn) + } + if d.IsForceNew != tc.forcenew { + t.Errorf("ForceNew not setup correctly for the condition-'%s', expected:%v; actual:%v", tn, tc.forcenew, d.IsForceNew) + } + } +} + func TestAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeUpdate(t *testing.T) { t.Parallel() @@ -69,3 +272,80 @@ resource "google_data_loss_prevention_stored_info_type" "basic" { } `, context) } + +func TestAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeGroupIndexUpdate(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "project": GetTestProjectFromEnv(), + } + + VcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV5ProviderFactories: ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckDataLossPreventionStoredInfoTypeDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeWithoutGroupIndex(context), + }, + { + ResourceName: "google_data_loss_prevention_stored_info_type.basic", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeStart(context), + }, + { + ResourceName: "google_data_loss_prevention_stored_info_type.basic", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeGroupIndexUpdate(context), + }, + { + ResourceName: "google_data_loss_prevention_stored_info_type.basic", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeWithoutGroupIndex(context), + }, + { + ResourceName: "google_data_loss_prevention_stored_info_type.basic", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeWithoutGroupIndex(context map[string]interface{}) string { + return Nprintf(` +resource "google_data_loss_prevention_stored_info_type" "basic" { + parent = "projects/%{project}" + description = "Description" + display_name = "Displayname" + + regex { + pattern = "patient" + } +} +`, context) +} + +func testAccDataLossPreventionStoredInfoType_dlpStoredInfoTypeGroupIndexUpdate(context map[string]interface{}) string { + return Nprintf(` +resource "google_data_loss_prevention_stored_info_type" "basic" { + parent = "projects/%{project}" + description = "Description" + display_name = "Displayname" + + regex { + pattern = "patient" + group_indexes = [3] + } +} +`, context) +}