diff --git a/.changelog/9081.txt b/.changelog/9081.txt new file mode 100644 index 0000000000..82d2c0105e --- /dev/null +++ b/.changelog/9081.txt @@ -0,0 +1,3 @@ +```release-note:new-resource +`google_scc_organization_custom_module` +``` diff --git a/google-beta/provider/provider.go b/google-beta/provider/provider.go index 51e5496c20..14367161a2 100644 --- a/google-beta/provider/provider.go +++ b/google-beta/provider/provider.go @@ -1122,9 +1122,9 @@ func DatasourceMapWithErrors() (map[string]*schema.Resource, error) { }) } -// Generated resources: 390 +// Generated resources: 391 // Generated IAM resources: 240 -// Total generated resources: 630 +// Total generated resources: 631 func ResourceMap() map[string]*schema.Resource { resourceMap, _ := ResourceMapWithErrors() return resourceMap @@ -1684,6 +1684,7 @@ func ResourceMapWithErrors() (map[string]*schema.Resource, error) { "google_scc_folder_custom_module": securitycenter.ResourceSecurityCenterFolderCustomModule(), "google_scc_mute_config": securitycenter.ResourceSecurityCenterMuteConfig(), "google_scc_notification_config": securitycenter.ResourceSecurityCenterNotificationConfig(), + "google_scc_organization_custom_module": securitycenter.ResourceSecurityCenterOrganizationCustomModule(), "google_scc_project_custom_module": securitycenter.ResourceSecurityCenterProjectCustomModule(), "google_scc_source": securitycenter.ResourceSecurityCenterSource(), "google_scc_source_iam_binding": tpgiamresource.ResourceIamBinding(securitycenter.SecurityCenterSourceIamSchema, securitycenter.SecurityCenterSourceIamUpdaterProducer, securitycenter.SecurityCenterSourceIdParseFunc), diff --git a/google-beta/services/securitycenter/resource_scc_folder_custom_module_generated_test.go b/google-beta/services/securitycenter/resource_scc_folder_custom_module_generated_test.go deleted file mode 100644 index 0880e032f0..0000000000 --- a/google-beta/services/securitycenter/resource_scc_folder_custom_module_generated_test.go +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// ---------------------------------------------------------------------------- -// -// *** 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 securitycenter_test - -import ( - "fmt" - "strings" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - - "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" - "github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar" - "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" - transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" -) - -func TestAccSecurityCenterFolderCustomModule_sccFolderCustomModuleBasicExample(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "org_id": envvar.GetTestOrgFromEnv(t), - "sleep": true, - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - ExternalProviders: map[string]resource.ExternalProvider{ - "random": {}, - "time": {}, - }, - CheckDestroy: testAccCheckSecurityCenterFolderCustomModuleDestroyProducer(t), - Steps: []resource.TestStep{ - { - Config: testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleBasicExample(context), - }, - { - ResourceName: "google_scc_folder_custom_module.example", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"folder"}, - }, - }, - }) -} - -func testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleBasicExample(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_folder" "folder" { - parent = "organizations/%{org_id}" - display_name = "tf-test-folder-name%{random_suffix}" -} - -resource "time_sleep" "wait_1_minute" { - depends_on = [google_folder.folder] - - create_duration = "1m" -} - -resource "google_scc_folder_custom_module" "example" { - folder = google_folder.folder.folder_id - display_name = "tf_test_basic_custom_module%{random_suffix}" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - description = "The rotation period of the identified cryptokey resource exceeds 30 days." - recommendation = "Set the rotation period to at most 30 days." - severity = "MEDIUM" - } - - - depends_on = [time_sleep.wait_1_minute] -} -`, context) -} - -func TestAccSecurityCenterFolderCustomModule_sccFolderCustomModuleFullExample(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "org_id": envvar.GetTestOrgFromEnv(t), - "sleep": true, - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - ExternalProviders: map[string]resource.ExternalProvider{ - "random": {}, - "time": {}, - }, - CheckDestroy: testAccCheckSecurityCenterFolderCustomModuleDestroyProducer(t), - Steps: []resource.TestStep{ - { - Config: testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleFullExample(context), - }, - { - ResourceName: "google_scc_folder_custom_module.example", - ImportState: true, - ImportStateVerify: true, - ImportStateVerifyIgnore: []string{"folder"}, - }, - }, - }) -} - -func testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleFullExample(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_folder" "folder" { - parent = "organizations/%{org_id}" - display_name = "tf-test-folder-name%{random_suffix}" -} - -resource "time_sleep" "wait_1_minute" { - depends_on = [google_folder.folder] - - create_duration = "1m" -} - -resource "google_scc_folder_custom_module" "example" { - folder = google_folder.folder.folder_id - display_name = "tf_test_full_custom_module%{random_suffix}" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - custom_output { - properties { - name = "duration" - value_expression { - expression = "resource.rotationPeriod" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - } - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - severity = "LOW" - description = "Description of the custom module" - recommendation = "Steps to resolve violation" - } - - - depends_on = [time_sleep.wait_1_minute] -} -`, context) -} - -func testAccCheckSecurityCenterFolderCustomModuleDestroyProducer(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_scc_folder_custom_module" { - continue - } - if strings.HasPrefix(name, "data.") { - continue - } - - config := acctest.GoogleProviderConfig(t) - - url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{SecurityCenterBasePath}}folders/{{folder}}/securityHealthAnalyticsSettings/customModules/{{name}}") - if err != nil { - return err - } - - billingProject := "" - - if config.BillingProject != "" { - billingProject = config.BillingProject - } - - _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: billingProject, - RawURL: url, - UserAgent: config.UserAgent, - }) - if err == nil { - return fmt.Errorf("SecurityCenterFolderCustomModule still exists at %s", url) - } - } - - return nil - } -} diff --git a/google-beta/services/securitycenter/resource_scc_folder_custom_module_test.go b/google-beta/services/securitycenter/resource_scc_folder_custom_module_test.go index ae53041c13..4682efd7bf 100644 --- a/google-beta/services/securitycenter/resource_scc_folder_custom_module_test.go +++ b/google-beta/services/securitycenter/resource_scc_folder_custom_module_test.go @@ -3,19 +3,27 @@ package securitycenter_test import ( + "fmt" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" "github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" ) -func TestAccSecurityCenterFolderCustomModule_sccFolderCustomModuleUpdate(t *testing.T) { +// Custom Module tests cannot be run in parallel without running into 409 Conflict reponses. +// Run them as individual steps of an update test instead. +func TestAccSecurityCenterFolderCustomModule(t *testing.T) { t.Parallel() context := map[string]interface{}{ "org_id": envvar.GetTestOrgFromEnv(t), + "sleep": true, "random_suffix": acctest.RandString(t, 10), } @@ -28,26 +36,116 @@ func TestAccSecurityCenterFolderCustomModule_sccFolderCustomModuleUpdate(t *test }, CheckDestroy: testAccCheckSecurityCenterFolderCustomModuleDestroyProducer(t), Steps: []resource.TestStep{ + { + Config: testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleBasicExample(context), + }, + { + ResourceName: "google_scc_folder_custom_module.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"folder"}, + }, { Config: testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleFullExample(context), }, { - ResourceName: "google_scc_folder_custom_module.example", - ImportState: true, - ImportStateVerify: true, + ResourceName: "google_scc_folder_custom_module.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"folder"}, }, { Config: testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleUpdate(context), }, { - ResourceName: "google_scc_folder_custom_module.example", - ImportState: true, - ImportStateVerify: true, + ResourceName: "google_scc_folder_custom_module.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"folder"}, }, }, }) } +func testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_folder" "folder" { + parent = "organizations/%{org_id}" + display_name = "tf-test-folder-name%{random_suffix}" +} + +resource "time_sleep" "wait_1_minute" { + depends_on = [google_folder.folder] + + create_duration = "1m" +} + +resource "google_scc_folder_custom_module" "example" { + folder = google_folder.folder.folder_id + display_name = "tf_test_basic_custom_module%{random_suffix}" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + description = "The rotation period of the identified cryptokey resource exceeds 30 days." + recommendation = "Set the rotation period to at most 30 days." + severity = "MEDIUM" + } + + + depends_on = [time_sleep.wait_1_minute] +} +`, context) +} + +func testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleFullExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_folder" "folder" { + parent = "organizations/%{org_id}" + display_name = "tf-test-folder-name%{random_suffix}" +} + +resource "google_scc_folder_custom_module" "example" { + folder = google_folder.folder.folder_id + display_name = "tf_test_full_custom_module%{random_suffix}" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + custom_output { + properties { + name = "duration" + value_expression { + expression = "resource.rotationPeriod" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + } + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + severity = "LOW" + description = "Description of the custom module" + recommendation = "Steps to resolve violation" + } +} +`, context) +} + func testAccSecurityCenterFolderCustomModule_sccFolderCustomModuleUpdate(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_folder" "folder" { @@ -89,3 +187,42 @@ resource "google_scc_folder_custom_module" "example" { } `, context) } + +func testAccCheckSecurityCenterFolderCustomModuleDestroyProducer(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_scc_folder_custom_module" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{SecurityCenterBasePath}}folders/{{folder}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: config.UserAgent, + }) + if err == nil { + return fmt.Errorf("SecurityCenterFolderCustomModule still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/services/securitycenter/resource_scc_organization_custom_module.go b/google-beta/services/securitycenter/resource_scc_organization_custom_module.go new file mode 100644 index 0000000000..8a87999b63 --- /dev/null +++ b/google-beta/services/securitycenter/resource_scc_organization_custom_module.go @@ -0,0 +1,962 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** 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 securitycenter + +import ( + "fmt" + "log" + "reflect" + "strings" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/verify" +) + +func ResourceSecurityCenterOrganizationCustomModule() *schema.Resource { + return &schema.Resource{ + Create: resourceSecurityCenterOrganizationCustomModuleCreate, + Read: resourceSecurityCenterOrganizationCustomModuleRead, + Update: resourceSecurityCenterOrganizationCustomModuleUpdate, + Delete: resourceSecurityCenterOrganizationCustomModuleDelete, + + Importer: &schema.ResourceImporter{ + State: resourceSecurityCenterOrganizationCustomModuleImport, + }, + + 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{ + "custom_config": { + Type: schema.TypeList, + Required: true, + Description: `The user specified custom configuration for the module.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "predicate": { + Type: schema.TypeList, + Required: true, + Description: `The CEL expression to evaluate to produce findings. When the expression evaluates +to true against a resource, a finding is generated.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "expression": { + Type: schema.TypeString, + Required: true, + Description: `Textual representation of an expression in Common Expression Language syntax.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Description of the expression. This is a longer text which describes the +expression, e.g. when hovered over it in a UI.`, + }, + "location": { + Type: schema.TypeString, + Optional: true, + Description: `String indicating the location of the expression for error reporting, e.g. a +file name and a position in the file.`, + }, + "title": { + Type: schema.TypeString, + Optional: true, + Description: `Title for the expression, i.e. a short string describing its purpose. This can +be used e.g. in UIs which allow to enter the expression.`, + }, + }, + }, + }, + "recommendation": { + Type: schema.TypeString, + Required: true, + Description: `An explanation of the recommended steps that security teams can take to resolve +the detected issue. This explanation is returned with each finding generated by +this module in the nextSteps property of the finding JSON.`, + }, + "resource_selector": { + Type: schema.TypeList, + Required: true, + Description: `The resource types that the custom module operates on. Each custom module +can specify up to 5 resource types.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "resource_types": { + Type: schema.TypeList, + Required: true, + Description: `The resource types to run the detector on.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "severity": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidateEnum([]string{"CRITICAL", "HIGH", "MEDIUM", "LOW"}), + Description: `The severity to assign to findings generated by the module. Possible values: ["CRITICAL", "HIGH", "MEDIUM", "LOW"]`, + }, + "custom_output": { + Type: schema.TypeList, + Optional: true, + Description: `Custom output properties.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "properties": { + Type: schema.TypeList, + Optional: true, + Description: `A list of custom output properties to add to the finding.`, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Optional: true, + Description: `Name of the property for the custom output.`, + }, + "value_expression": { + Type: schema.TypeList, + Optional: true, + Description: `The CEL expression for the custom output. A resource property can be specified +to return the value of the property or a text string enclosed in quotation marks.`, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "expression": { + Type: schema.TypeString, + Required: true, + Description: `Textual representation of an expression in Common Expression Language syntax.`, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Description of the expression. This is a longer text which describes the +expression, e.g. when hovered over it in a UI.`, + }, + "location": { + Type: schema.TypeString, + Optional: true, + Description: `String indicating the location of the expression for error reporting, e.g. a +file name and a position in the file.`, + }, + "title": { + Type: schema.TypeString, + Optional: true, + Description: `Title for the expression, i.e. a short string describing its purpose. This can +be used e.g. in UIs which allow to enter the expression.`, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "description": { + Type: schema.TypeString, + Optional: true, + Description: `Text that describes the vulnerability or misconfiguration that the custom +module detects. This explanation is returned with each finding instance to +help investigators understand the detected issue. The text must be enclosed in quotation marks.`, + }, + }, + }, + }, + "display_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateFunc: verify.ValidateRegexp(`^[a-z][\w_]{0,127}$`), + Description: `The display name of the Security Health Analytics custom module. This +display name becomes the finding category for all findings that are +returned by this custom module. The display name must be between 1 and +128 characters, start with a lowercase letter, and contain alphanumeric +characters or underscores only.`, + }, + "enablement_state": { + Type: schema.TypeString, + Required: true, + ValidateFunc: verify.ValidateEnum([]string{"ENABLED", "DISABLED"}), + Description: `The enablement state of the custom module. Possible values: ["ENABLED", "DISABLED"]`, + }, + "organization": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: `Numerical ID of the parent organization.`, + }, + "ancestor_module": { + Type: schema.TypeString, + Computed: true, + Description: `If empty, indicates that the custom module was created in the organization, folder, +or project in which you are viewing the custom module. Otherwise, ancestor_module +specifies the organization or folder from which the custom module is inherited.`, + }, + "last_editor": { + Type: schema.TypeString, + Computed: true, + Description: `The editor that last updated the custom module.`, + }, + "name": { + Type: schema.TypeString, + Computed: true, + Description: `The resource name of the custom module. Its format is "organizations/{org_id}/securityHealthAnalyticsSettings/customModules/{customModule}". +The id {customModule} is server-generated and is not user settable. It will be a numeric id containing 1-20 digits.`, + }, + "update_time": { + Type: schema.TypeString, + Computed: true, + Description: `The time at which the custom module was last updated. + +A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and +up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z".`, + }, + }, + UseJSONNumber: true, + } +} + +func resourceSecurityCenterOrganizationCustomModuleCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + displayNameProp, err := expandSecurityCenterOrganizationCustomModuleDisplayName(d.Get("display_name"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("display_name"); !tpgresource.IsEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + enablementStateProp, err := expandSecurityCenterOrganizationCustomModuleEnablementState(d.Get("enablement_state"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enablement_state"); !tpgresource.IsEmptyValue(reflect.ValueOf(enablementStateProp)) && (ok || !reflect.DeepEqual(v, enablementStateProp)) { + obj["enablementState"] = enablementStateProp + } + customConfigProp, err := expandSecurityCenterOrganizationCustomModuleCustomConfig(d.Get("custom_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("custom_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(customConfigProp)) && (ok || !reflect.DeepEqual(v, customConfigProp)) { + obj["customConfig"] = customConfigProp + } + + lockName, err := tpgresource.ReplaceVars(d, config, "organizations/{{organization}}/securityHealthAnalyticsSettings/customModules") + if err != nil { + return err + } + transport_tpg.MutexStore.Lock(lockName) + defer transport_tpg.MutexStore.Unlock(lockName) + + url, err := tpgresource.ReplaceVars(d, config, "{{SecurityCenterBasePath}}organizations/{{organization}}/securityHealthAnalyticsSettings/customModules") + if err != nil { + return err + } + + log.Printf("[DEBUG] Creating new OrganizationCustomModule: %#v", obj) + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "POST", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + }) + if err != nil { + return fmt.Errorf("Error creating OrganizationCustomModule: %s", err) + } + if err := d.Set("name", flattenSecurityCenterOrganizationCustomModuleName(res["name"], d, config)); err != nil { + return fmt.Errorf(`Error setting computed identity field "name": %s`, err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + log.Printf("[DEBUG] Finished creating OrganizationCustomModule %q: %#v", d.Id(), res) + + return resourceSecurityCenterOrganizationCustomModuleRead(d, meta) +} + +func resourceSecurityCenterOrganizationCustomModuleRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{SecurityCenterBasePath}}organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("SecurityCenterOrganizationCustomModule %q", d.Id())) + } + + if err := d.Set("name", flattenSecurityCenterOrganizationCustomModuleName(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationCustomModule: %s", err) + } + if err := d.Set("display_name", flattenSecurityCenterOrganizationCustomModuleDisplayName(res["displayName"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationCustomModule: %s", err) + } + if err := d.Set("enablement_state", flattenSecurityCenterOrganizationCustomModuleEnablementState(res["enablementState"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationCustomModule: %s", err) + } + if err := d.Set("update_time", flattenSecurityCenterOrganizationCustomModuleUpdateTime(res["updateTime"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationCustomModule: %s", err) + } + if err := d.Set("last_editor", flattenSecurityCenterOrganizationCustomModuleLastEditor(res["lastEditor"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationCustomModule: %s", err) + } + if err := d.Set("ancestor_module", flattenSecurityCenterOrganizationCustomModuleAncestorModule(res["ancestorModule"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationCustomModule: %s", err) + } + if err := d.Set("custom_config", flattenSecurityCenterOrganizationCustomModuleCustomConfig(res["customConfig"], d, config)); err != nil { + return fmt.Errorf("Error reading OrganizationCustomModule: %s", err) + } + + return nil +} + +func resourceSecurityCenterOrganizationCustomModuleUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + obj := make(map[string]interface{}) + enablementStateProp, err := expandSecurityCenterOrganizationCustomModuleEnablementState(d.Get("enablement_state"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("enablement_state"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, enablementStateProp)) { + obj["enablementState"] = enablementStateProp + } + customConfigProp, err := expandSecurityCenterOrganizationCustomModuleCustomConfig(d.Get("custom_config"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("custom_config"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, customConfigProp)) { + obj["customConfig"] = customConfigProp + } + + lockName, err := tpgresource.ReplaceVars(d, config, "organizations/{{organization}}/securityHealthAnalyticsSettings/customModules") + if err != nil { + return err + } + transport_tpg.MutexStore.Lock(lockName) + defer transport_tpg.MutexStore.Unlock(lockName) + + url, err := tpgresource.ReplaceVars(d, config, "{{SecurityCenterBasePath}}organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating OrganizationCustomModule %q: %#v", d.Id(), obj) + updateMask := []string{} + + if d.HasChange("enablement_state") { + updateMask = append(updateMask, "enablementState") + } + + if d.HasChange("custom_config") { + updateMask = append(updateMask, "customConfig") + } + // updateMask is a URL parameter but not present in the schema, so ReplaceVars + // won't set it + url, err = transport_tpg.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 := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), + }) + + if err != nil { + return fmt.Errorf("Error updating OrganizationCustomModule %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating OrganizationCustomModule %q: %#v", d.Id(), res) + } + + return resourceSecurityCenterOrganizationCustomModuleRead(d, meta) +} + +func resourceSecurityCenterOrganizationCustomModuleDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + lockName, err := tpgresource.ReplaceVars(d, config, "organizations/{{organization}}/securityHealthAnalyticsSettings/customModules") + if err != nil { + return err + } + transport_tpg.MutexStore.Lock(lockName) + defer transport_tpg.MutexStore.Unlock(lockName) + + url, err := tpgresource.ReplaceVars(d, config, "{{SecurityCenterBasePath}}organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return err + } + + var obj map[string]interface{} + log.Printf("[DEBUG] Deleting OrganizationCustomModule %q", d.Id()) + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutDelete), + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "OrganizationCustomModule") + } + + log.Printf("[DEBUG] Finished deleting OrganizationCustomModule %q: %#v", d.Id(), res) + return nil +} + +func resourceSecurityCenterOrganizationCustomModuleImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "organizations/(?P[^/]+)/securityHealthAnalyticsSettings/customModules/(?P[^/]+)", + "(?P[^/]+)/(?P[^/]+)", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenSecurityCenterOrganizationCustomModuleName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.NameFromSelfLinkStateFunc(v) +} + +func flattenSecurityCenterOrganizationCustomModuleDisplayName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleEnablementState(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleUpdateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleLastEditor(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleAncestorModule(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfig(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["predicate"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicate(original["predicate"], d, config) + transformed["custom_output"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutput(original["customOutput"], d, config) + transformed["resource_selector"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigResourceSelector(original["resourceSelector"], d, config) + transformed["severity"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigSeverity(original["severity"], d, config) + transformed["description"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigDescription(original["description"], d, config) + transformed["recommendation"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigRecommendation(original["recommendation"], d, config) + return []interface{}{transformed} +} +func flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicate(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["expression"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateExpression(original["expression"], d, config) + transformed["title"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateTitle(original["title"], d, config) + transformed["description"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateDescription(original["description"], d, config) + transformed["location"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateLocation(original["location"], d, config) + return []interface{}{transformed} +} +func flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateExpression(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateTitle(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigPredicateLocation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutput(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["properties"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputProperties(original["properties"], d, config) + return []interface{}{transformed} +} +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputProperties(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + l := v.([]interface{}) + transformed := make([]interface{}, 0, len(l)) + for _, raw := range l { + original := raw.(map[string]interface{}) + if len(original) < 1 { + // Do not include empty json objects coming back from the api + continue + } + transformed = append(transformed, map[string]interface{}{ + "name": flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesName(original["name"], d, config), + "value_expression": flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpression(original["valueExpression"], d, config), + }) + } + return transformed +} +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpression(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["expression"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionExpression(original["expression"], d, config) + transformed["title"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionTitle(original["title"], d, config) + transformed["description"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionDescription(original["description"], d, config) + transformed["location"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionLocation(original["location"], d, config) + return []interface{}{transformed} +} +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionExpression(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionTitle(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionLocation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigResourceSelector(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return nil + } + original := v.(map[string]interface{}) + if len(original) == 0 { + return nil + } + transformed := make(map[string]interface{}) + transformed["resource_types"] = + flattenSecurityCenterOrganizationCustomModuleCustomConfigResourceSelectorResourceTypes(original["resourceTypes"], d, config) + return []interface{}{transformed} +} +func flattenSecurityCenterOrganizationCustomModuleCustomConfigResourceSelectorResourceTypes(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigSeverity(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func flattenSecurityCenterOrganizationCustomModuleCustomConfigRecommendation(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + return v +} + +func expandSecurityCenterOrganizationCustomModuleDisplayName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleEnablementState(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfig(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.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{}) + + transformedPredicate, err := expandSecurityCenterOrganizationCustomModuleCustomConfigPredicate(original["predicate"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedPredicate); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["predicate"] = transformedPredicate + } + + transformedCustomOutput, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutput(original["custom_output"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedCustomOutput); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["customOutput"] = transformedCustomOutput + } + + transformedResourceSelector, err := expandSecurityCenterOrganizationCustomModuleCustomConfigResourceSelector(original["resource_selector"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceSelector); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceSelector"] = transformedResourceSelector + } + + transformedSeverity, err := expandSecurityCenterOrganizationCustomModuleCustomConfigSeverity(original["severity"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedSeverity); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["severity"] = transformedSeverity + } + + transformedDescription, err := expandSecurityCenterOrganizationCustomModuleCustomConfigDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedRecommendation, err := expandSecurityCenterOrganizationCustomModuleCustomConfigRecommendation(original["recommendation"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedRecommendation); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["recommendation"] = transformedRecommendation + } + + return transformed, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigPredicate(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.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{}) + + transformedExpression, err := expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateExpression(original["expression"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExpression); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["expression"] = transformedExpression + } + + transformedTitle, err := expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateTitle(original["title"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTitle); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["title"] = transformedTitle + } + + transformedDescription, err := expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedLocation, err := expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateLocation(original["location"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocation); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["location"] = transformedLocation + } + + return transformed, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateExpression(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateTitle(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigPredicateLocation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutput(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.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{}) + + transformedProperties, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputProperties(original["properties"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedProperties); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["properties"] = transformedProperties + } + + return transformed, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputProperties(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedName, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["name"] = transformedName + } + + transformedValueExpression, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpression(original["value_expression"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedValueExpression); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["valueExpression"] = transformedValueExpression + } + + req = append(req, transformed) + } + return req, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpression(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.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{}) + + transformedExpression, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionExpression(original["expression"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedExpression); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["expression"] = transformedExpression + } + + transformedTitle, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionTitle(original["title"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedTitle); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["title"] = transformedTitle + } + + transformedDescription, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedLocation, err := expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionLocation(original["location"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedLocation); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["location"] = transformedLocation + } + + return transformed, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionExpression(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionTitle(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigCustomOutputPropertiesValueExpressionLocation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigResourceSelector(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.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{}) + + transformedResourceTypes, err := expandSecurityCenterOrganizationCustomModuleCustomConfigResourceSelectorResourceTypes(original["resource_types"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedResourceTypes); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["resourceTypes"] = transformedResourceTypes + } + + return transformed, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigResourceSelectorResourceTypes(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigSeverity(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandSecurityCenterOrganizationCustomModuleCustomConfigRecommendation(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/google-beta/services/securitycenter/resource_scc_organization_custom_module_sweeper.go b/google-beta/services/securitycenter/resource_scc_organization_custom_module_sweeper.go new file mode 100644 index 0000000000..7cd1c32585 --- /dev/null +++ b/google-beta/services/securitycenter/resource_scc_organization_custom_module_sweeper.go @@ -0,0 +1,139 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** 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 securitycenter + +import ( + "context" + "log" + "strings" + "testing" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/sweeper" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +func init() { + sweeper.AddTestSweepers("SecurityCenterOrganizationCustomModule", testSweepSecurityCenterOrganizationCustomModule) +} + +// At the time of writing, the CI only passes us-central1 as the region +func testSweepSecurityCenterOrganizationCustomModule(region string) error { + resourceName := "SecurityCenterOrganizationCustomModule" + log.Printf("[INFO][SWEEPER_LOG] Starting sweeper for %s", resourceName) + + config, err := sweeper.SharedConfigForRegion(region) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error getting shared config for region: %s", err) + return err + } + + err = config.LoadAndValidate(context.Background()) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error loading: %s", err) + return err + } + + t := &testing.T{} + billingId := envvar.GetTestBillingAccountFromEnv(t) + + // Setup variables to replace in list template + d := &tpgresource.ResourceDataMock{ + FieldsInSchema: map[string]interface{}{ + "project": config.Project, + "region": region, + "location": region, + "zone": "-", + "billing_account": billingId, + }, + } + + listTemplate := strings.Split("https://securitycenter.googleapis.com/v1/organizations/{{organization}}/securityHealthAnalyticsSettings/customModules", "?")[0] + listUrl, err := tpgresource.ReplaceVars(d, config, listTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing sweeper list url: %s", err) + return nil + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: config.Project, + RawURL: listUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error in response from request %s: %s", listUrl, err) + return nil + } + + resourceList, ok := res["organizationCustomModules"] + if !ok { + log.Printf("[INFO][SWEEPER_LOG] Nothing found in response.") + return nil + } + + rl := resourceList.([]interface{}) + + log.Printf("[INFO][SWEEPER_LOG] Found %d items in %s list response.", len(rl), resourceName) + // Keep count of items that aren't sweepable for logging. + nonPrefixCount := 0 + for _, ri := range rl { + obj := ri.(map[string]interface{}) + if obj["name"] == nil { + log.Printf("[INFO][SWEEPER_LOG] %s resource name was nil", resourceName) + return nil + } + + name := tpgresource.GetResourceNameFromSelfLink(obj["name"].(string)) + // Skip resources that shouldn't be sweeped + if !sweeper.IsSweepableTestResource(name) { + nonPrefixCount++ + continue + } + + deleteTemplate := "https://securitycenter.googleapis.com/v1/organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}" + deleteUrl, err := tpgresource.ReplaceVars(d, config, deleteTemplate) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] error preparing delete url: %s", err) + return nil + } + deleteUrl = deleteUrl + name + + // Don't wait on operations as we may have a lot to delete + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "DELETE", + Project: config.Project, + RawURL: deleteUrl, + UserAgent: config.UserAgent, + }) + if err != nil { + log.Printf("[INFO][SWEEPER_LOG] Error deleting for url %s : %s", deleteUrl, err) + } else { + log.Printf("[INFO][SWEEPER_LOG] Sent delete request for %s resource: %s", resourceName, name) + } + } + + if nonPrefixCount > 0 { + log.Printf("[INFO][SWEEPER_LOG] %d items were non-sweepable and skipped.", nonPrefixCount) + } + + return nil +} diff --git a/google-beta/services/securitycenter/resource_scc_organization_custom_module_test.go b/google-beta/services/securitycenter/resource_scc_organization_custom_module_test.go new file mode 100644 index 0000000000..355a39f14f --- /dev/null +++ b/google-beta/services/securitycenter/resource_scc_organization_custom_module_test.go @@ -0,0 +1,199 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package securitycenter_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +// Custom Module tests cannot be run in parallel without running into 409 Conflict reponses. +// Run them as individual steps of an update test instead. +func TestAccSecurityCenterOrganizationCustomModule(t *testing.T) { + t.Parallel() + + context := map[string]interface{}{ + "org_id": envvar.GetTestOrgFromEnv(t), + "random_suffix": acctest.RandString(t, 10), + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckSecurityCenterOrganizationCustomModuleDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccSecurityCenterOrganizationCustomModule_sccOrganizationCustomModuleBasicExample(context), + }, + { + ResourceName: "google_scc_organization_custom_module.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"organization"}, + }, + { + Config: testAccSecurityCenterOrganizationCustomModule_sccOrganizationCustomModuleFullExample(context), + }, + { + ResourceName: "google_scc_organization_custom_module.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"organization"}, + }, + { + Config: testAccSecurityCenterOrganizationCustomModule_sccOrganizationCustomModuleUpdate(context), + }, + { + ResourceName: "google_scc_organization_custom_module.example", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"organization"}, + }, + }, + }) +} + +func testAccSecurityCenterOrganizationCustomModule_sccOrganizationCustomModuleBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_scc_organization_custom_module" "example" { + organization = "%{org_id}" + display_name = "tf_test_basic_custom_module%{random_suffix}" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + description = "The rotation period of the identified cryptokey resource exceeds 30 days." + recommendation = "Set the rotation period to at most 30 days." + severity = "MEDIUM" + } +} +`, context) +} + +func testAccSecurityCenterOrganizationCustomModule_sccOrganizationCustomModuleFullExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_scc_organization_custom_module" "example" { + organization = "%{org_id}" + display_name = "tf_test_full_custom_module%{random_suffix}" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + custom_output { + properties { + name = "duration" + value_expression { + expression = "resource.rotationPeriod" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + } + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + severity = "LOW" + description = "Description of the custom module" + recommendation = "Steps to resolve violation" + } +} +`, context) +} + +func testAccSecurityCenterOrganizationCustomModule_sccOrganizationCustomModuleUpdate(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_scc_organization_custom_module" "example" { + organization = "%{org_id}" + display_name = "tf_test_full_custom_module%{random_suffix}" + enablement_state = "DISABLED" + custom_config { + predicate { + expression = "resource.name == \"updated-name\"" + title = "Updated expression title" + description = "Updated description of the expression" + location = "Updated location of the expression" + } + custom_output { + properties { + name = "violation" + value_expression { + expression = "resource.name" + title = "Updated expression title" + description = "Updated description of the expression" + location = "Updated location of the expression" + } + } + } + resource_selector { + resource_types = [ + "compute.googleapis.com/Instance", + ] + } + severity = "CRITICAL" + description = "Updated description of the custom module" + recommendation = "Updated steps to resolve violation" + } +} +`, context) +} + +func testAccCheckSecurityCenterOrganizationCustomModuleDestroyProducer(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_scc_organization_custom_module" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{SecurityCenterBasePath}}organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: config.UserAgent, + }) + if err == nil { + return fmt.Errorf("SecurityCenterOrganizationCustomModule still exists at %s", url) + } + } + + return nil + } +} diff --git a/google-beta/services/securitycenter/resource_scc_project_custom_module_generated_test.go b/google-beta/services/securitycenter/resource_scc_project_custom_module_generated_test.go deleted file mode 100644 index 0f07478ba4..0000000000 --- a/google-beta/services/securitycenter/resource_scc_project_custom_module_generated_test.go +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -// ---------------------------------------------------------------------------- -// -// *** 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 securitycenter_test - -import ( - "fmt" - "strings" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" - - "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" - "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" - transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" -) - -func TestAccSecurityCenterProjectCustomModule_sccProjectCustomModuleBasicExample(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - CheckDestroy: testAccCheckSecurityCenterProjectCustomModuleDestroyProducer(t), - Steps: []resource.TestStep{ - { - Config: testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleBasicExample(context), - }, - { - ResourceName: "google_scc_project_custom_module.example", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleBasicExample(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_scc_project_custom_module" "example" { - display_name = "tf_test_basic_custom_module%{random_suffix}" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - description = "The rotation period of the identified cryptokey resource exceeds 30 days." - recommendation = "Set the rotation period to at most 30 days." - severity = "MEDIUM" - } -} -`, context) -} - -func TestAccSecurityCenterProjectCustomModule_sccProjectCustomModuleFullExample(t *testing.T) { - t.Parallel() - - context := map[string]interface{}{ - "random_suffix": acctest.RandString(t, 10), - } - - acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, - ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), - CheckDestroy: testAccCheckSecurityCenterProjectCustomModuleDestroyProducer(t), - Steps: []resource.TestStep{ - { - Config: testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleFullExample(context), - }, - { - ResourceName: "google_scc_project_custom_module.example", - ImportState: true, - ImportStateVerify: true, - }, - }, - }) -} - -func testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleFullExample(context map[string]interface{}) string { - return acctest.Nprintf(` -resource "google_scc_project_custom_module" "example" { - display_name = "tf_test_full_custom_module%{random_suffix}" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - custom_output { - properties { - name = "duration" - value_expression { - expression = "resource.rotationPeriod" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - } - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - severity = "LOW" - description = "Description of the custom module" - recommendation = "Steps to resolve violation" - } -} -`, context) -} - -func testAccCheckSecurityCenterProjectCustomModuleDestroyProducer(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_scc_project_custom_module" { - continue - } - if strings.HasPrefix(name, "data.") { - continue - } - - config := acctest.GoogleProviderConfig(t) - - url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{SecurityCenterBasePath}}projects/{{project}}/securityHealthAnalyticsSettings/customModules/{{name}}") - if err != nil { - return err - } - - billingProject := "" - - if config.BillingProject != "" { - billingProject = config.BillingProject - } - - _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ - Config: config, - Method: "GET", - Project: billingProject, - RawURL: url, - UserAgent: config.UserAgent, - }) - if err == nil { - return fmt.Errorf("SecurityCenterProjectCustomModule still exists at %s", url) - } - } - - return nil - } -} diff --git a/google-beta/services/securitycenter/resource_scc_project_custom_module_test.go b/google-beta/services/securitycenter/resource_scc_project_custom_module_test.go index 89478babbc..bb337532e8 100644 --- a/google-beta/services/securitycenter/resource_scc_project_custom_module_test.go +++ b/google-beta/services/securitycenter/resource_scc_project_custom_module_test.go @@ -3,14 +3,21 @@ package securitycenter_test import ( + "fmt" + "strings" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" ) -func TestAccSecurityCenterProjectCustomModule_sccProjectCustomModuleUpdate(t *testing.T) { +// Custom Module tests cannot be run in parallel without running into 409 Conflict reponses. +// Run them as individual steps of an update test instead. +func TestAccSecurityCenterProjectCustomModule(t *testing.T) { t.Parallel() context := map[string]interface{}{ @@ -22,6 +29,14 @@ func TestAccSecurityCenterProjectCustomModule_sccProjectCustomModuleUpdate(t *te ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccCheckSecurityCenterProjectCustomModuleDestroyProducer(t), Steps: []resource.TestStep{ + { + Config: testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleBasicExample(context), + }, + { + ResourceName: "google_scc_project_custom_module.example", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleFullExample(context), }, @@ -42,6 +57,64 @@ func TestAccSecurityCenterProjectCustomModule_sccProjectCustomModuleUpdate(t *te }) } +func testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleBasicExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_scc_project_custom_module" "example" { + display_name = "tf_test_basic_custom_module%{random_suffix}" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + description = "The rotation period of the identified cryptokey resource exceeds 30 days." + recommendation = "Set the rotation period to at most 30 days." + severity = "MEDIUM" + } +} +`, context) +} + +func testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleFullExample(context map[string]interface{}) string { + return acctest.Nprintf(` +resource "google_scc_project_custom_module" "example" { + display_name = "tf_test_full_custom_module%{random_suffix}" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + custom_output { + properties { + name = "duration" + value_expression { + expression = "resource.rotationPeriod" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + } + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + severity = "LOW" + description = "Description of the custom module" + recommendation = "Steps to resolve violation" + } +} +`, context) +} + func testAccSecurityCenterProjectCustomModule_sccProjectCustomModuleUpdate(context map[string]interface{}) string { return acctest.Nprintf(` resource "google_scc_project_custom_module" "example" { @@ -77,3 +150,42 @@ resource "google_scc_project_custom_module" "example" { } `, context) } + +func testAccCheckSecurityCenterProjectCustomModuleDestroyProducer(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_scc_project_custom_module" { + continue + } + if strings.HasPrefix(name, "data.") { + continue + } + + config := acctest.GoogleProviderConfig(t) + + url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{SecurityCenterBasePath}}projects/{{project}}/securityHealthAnalyticsSettings/customModules/{{name}}") + if err != nil { + return err + } + + billingProject := "" + + if config.BillingProject != "" { + billingProject = config.BillingProject + } + + _, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: config.UserAgent, + }) + if err == nil { + return fmt.Errorf("SecurityCenterProjectCustomModule still exists at %s", url) + } + } + + return nil + } +} diff --git a/website/docs/r/scc_folder_custom_module.html.markdown b/website/docs/r/scc_folder_custom_module.html.markdown index 54a0672f0e..dd5db00a9f 100644 --- a/website/docs/r/scc_folder_custom_module.html.markdown +++ b/website/docs/r/scc_folder_custom_module.html.markdown @@ -43,23 +43,22 @@ resource "google_folder" "folder" { } resource "google_scc_folder_custom_module" "example" { - folder = google_folder.folder.folder_id - display_name = "basic_custom_module" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - description = "The rotation period of the identified cryptokey resource exceeds 30 days." - recommendation = "Set the rotation period to at most 30 days." - severity = "MEDIUM" - } - + folder = google_folder.folder.folder_id + display_name = "basic_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + description = "The rotation period of the identified cryptokey resource exceeds 30 days." + recommendation = "Set the rotation period to at most 30 days." + severity = "MEDIUM" + } } ``` ## Example Usage - Scc Folder Custom Module Full @@ -72,37 +71,36 @@ resource "google_folder" "folder" { } resource "google_scc_folder_custom_module" "example" { - folder = google_folder.folder.folder_id - display_name = "full_custom_module" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - custom_output { - properties { - name = "duration" - value_expression { - expression = "resource.rotationPeriod" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - } - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - severity = "LOW" - description = "Description of the custom module" - recommendation = "Steps to resolve violation" - } - + folder = google_folder.folder.folder_id + display_name = "full_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + custom_output { + properties { + name = "duration" + value_expression { + expression = "resource.rotationPeriod" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + } + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + severity = "LOW" + description = "Description of the custom module" + recommendation = "Steps to resolve violation" + } } ``` diff --git a/website/docs/r/scc_organization_custom_module.html.markdown b/website/docs/r/scc_organization_custom_module.html.markdown new file mode 100644 index 0000000000..c18adfb423 --- /dev/null +++ b/website/docs/r/scc_organization_custom_module.html.markdown @@ -0,0 +1,276 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** 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. +# +# ---------------------------------------------------------------------------- +subcategory: "Security Command Center (SCC)" +description: |- + Represents an instance of a Security Health Analytics custom module, including + its full module name, display name, enablement state, and last updated time. +--- + +# google\_scc\_organization\_custom\_module + +Represents an instance of a Security Health Analytics custom module, including +its full module name, display name, enablement state, and last updated time. +You can create a custom module at the organization, folder, or project level. +Custom modules that you create at the organization or folder level are inherited +by the child folders and projects. + + +To get more information about OrganizationCustomModule, see: + +* [API documentation](https://cloud.google.com/security-command-center/docs/reference/rest/v1/organizations.securityHealthAnalyticsSettings.customModules) +* How-to Guides + * [Overview of custom modules for Security Health Analytics](https://cloud.google.com/security-command-center/docs/custom-modules-sha-overview) + +## Example Usage - Scc Organization Custom Module Basic + + +```hcl +resource "google_scc_organization_custom_module" "example" { + organization = "123456789" + display_name = "basic_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + description = "The rotation period of the identified cryptokey resource exceeds 30 days." + recommendation = "Set the rotation period to at most 30 days." + severity = "MEDIUM" + } +} +``` +## Example Usage - Scc Organization Custom Module Full + + +```hcl +resource "google_scc_organization_custom_module" "example" { + organization = "123456789" + display_name = "full_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + custom_output { + properties { + name = "duration" + value_expression { + expression = "resource.rotationPeriod" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + } + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + severity = "LOW" + description = "Description of the custom module" + recommendation = "Steps to resolve violation" + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `display_name` - + (Required) + The display name of the Security Health Analytics custom module. This + display name becomes the finding category for all findings that are + returned by this custom module. The display name must be between 1 and + 128 characters, start with a lowercase letter, and contain alphanumeric + characters or underscores only. + +* `enablement_state` - + (Required) + The enablement state of the custom module. + Possible values are: `ENABLED`, `DISABLED`. + +* `custom_config` - + (Required) + The user specified custom configuration for the module. + Structure is [documented below](#nested_custom_config). + +* `organization` - + (Required) + Numerical ID of the parent organization. + + +The `custom_config` block supports: + +* `predicate` - + (Required) + The CEL expression to evaluate to produce findings. When the expression evaluates + to true against a resource, a finding is generated. + Structure is [documented below](#nested_predicate). + +* `custom_output` - + (Optional) + Custom output properties. + Structure is [documented below](#nested_custom_output). + +* `resource_selector` - + (Required) + The resource types that the custom module operates on. Each custom module + can specify up to 5 resource types. + Structure is [documented below](#nested_resource_selector). + +* `severity` - + (Required) + The severity to assign to findings generated by the module. + Possible values are: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`. + +* `description` - + (Optional) + Text that describes the vulnerability or misconfiguration that the custom + module detects. This explanation is returned with each finding instance to + help investigators understand the detected issue. The text must be enclosed in quotation marks. + +* `recommendation` - + (Required) + An explanation of the recommended steps that security teams can take to resolve + the detected issue. This explanation is returned with each finding generated by + this module in the nextSteps property of the finding JSON. + + +The `predicate` block supports: + +* `expression` - + (Required) + Textual representation of an expression in Common Expression Language syntax. + +* `title` - + (Optional) + Title for the expression, i.e. a short string describing its purpose. This can + be used e.g. in UIs which allow to enter the expression. + +* `description` - + (Optional) + Description of the expression. This is a longer text which describes the + expression, e.g. when hovered over it in a UI. + +* `location` - + (Optional) + String indicating the location of the expression for error reporting, e.g. a + file name and a position in the file. + +The `custom_output` block supports: + +* `properties` - + (Optional) + A list of custom output properties to add to the finding. + Structure is [documented below](#nested_properties). + + +The `properties` block supports: + +* `name` - + (Optional) + Name of the property for the custom output. + +* `value_expression` - + (Optional) + The CEL expression for the custom output. A resource property can be specified + to return the value of the property or a text string enclosed in quotation marks. + Structure is [documented below](#nested_value_expression). + + +The `value_expression` block supports: + +* `expression` - + (Required) + Textual representation of an expression in Common Expression Language syntax. + +* `title` - + (Optional) + Title for the expression, i.e. a short string describing its purpose. This can + be used e.g. in UIs which allow to enter the expression. + +* `description` - + (Optional) + Description of the expression. This is a longer text which describes the + expression, e.g. when hovered over it in a UI. + +* `location` - + (Optional) + String indicating the location of the expression for error reporting, e.g. a + file name and a position in the file. + +The `resource_selector` block supports: + +* `resource_types` - + (Required) + The resource types to run the detector on. + +- - - + + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}}` + +* `name` - + The resource name of the custom module. Its format is "organizations/{org_id}/securityHealthAnalyticsSettings/customModules/{customModule}". + The id {customModule} is server-generated and is not user settable. It will be a numeric id containing 1-20 digits. + +* `update_time` - + The time at which the custom module was last updated. + A timestamp in RFC3339 UTC "Zulu" format, with nanosecond resolution and + up to nine fractional digits. Examples: "2014-10-02T15:01:23Z" and "2014-10-02T15:01:23.045123456Z". + +* `last_editor` - + The editor that last updated the custom module. + +* `ancestor_module` - + If empty, indicates that the custom module was created in the organization, folder, + or project in which you are viewing the custom module. Otherwise, ancestor_module + specifies the organization or folder from which the custom module is inherited. + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +OrganizationCustomModule can be imported using any of these accepted formats: + +``` +$ terraform import google_scc_organization_custom_module.default organizations/{{organization}}/securityHealthAnalyticsSettings/customModules/{{name}} +$ terraform import google_scc_organization_custom_module.default {{organization}}/{{name}} +``` diff --git a/website/docs/r/scc_project_custom_module.html.markdown b/website/docs/r/scc_project_custom_module.html.markdown index 0b6ce94fb6..0da8c7eab7 100644 --- a/website/docs/r/scc_project_custom_module.html.markdown +++ b/website/docs/r/scc_project_custom_module.html.markdown @@ -33,72 +33,62 @@ To get more information about ProjectCustomModule, see: * How-to Guides * [Overview of custom modules for Security Health Analytics](https://cloud.google.com/security-command-center/docs/custom-modules-sha-overview) - ## Example Usage - Scc Project Custom Module Basic ```hcl resource "google_scc_project_custom_module" "example" { - display_name = "basic_custom_module" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - description = "The rotation period of the identified cryptokey resource exceeds 30 days." - recommendation = "Set the rotation period to at most 30 days." - severity = "MEDIUM" - } + display_name = "basic_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + description = "The rotation period of the identified cryptokey resource exceeds 30 days." + recommendation = "Set the rotation period to at most 30 days." + severity = "MEDIUM" + } } ``` - ## Example Usage - Scc Project Custom Module Full ```hcl resource "google_scc_project_custom_module" "example" { - display_name = "full_custom_module" - enablement_state = "ENABLED" - custom_config { - predicate { - expression = "resource.rotationPeriod > duration(\"2592000s\")" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - custom_output { - properties { - name = "duration" - value_expression { - expression = "resource.rotationPeriod" - title = "Purpose of the expression" - description = "description of the expression" - location = "location of the expression" - } - } - } - resource_selector { - resource_types = [ - "cloudkms.googleapis.com/CryptoKey", - ] - } - severity = "LOW" - description = "Description of the custom module" - recommendation = "Steps to resolve violation" - } + display_name = "full_custom_module" + enablement_state = "ENABLED" + custom_config { + predicate { + expression = "resource.rotationPeriod > duration(\"2592000s\")" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + custom_output { + properties { + name = "duration" + value_expression { + expression = "resource.rotationPeriod" + title = "Purpose of the expression" + description = "description of the expression" + location = "location of the expression" + } + } + } + resource_selector { + resource_types = [ + "cloudkms.googleapis.com/CryptoKey", + ] + } + severity = "LOW" + description = "Description of the custom module" + recommendation = "Steps to resolve violation" + } } ```