From 7268af57edd218d730735c7118cf3cbae7e1493b Mon Sep 17 00:00:00 2001
From: The Magician <magic-modules@google.com>
Date: Mon, 5 Aug 2024 15:20:37 -0700
Subject: [PATCH] Add project level mute config for SCC v2 (#11316) (#7881)

[upstream:05e0a2ec0048d92dbf8b85c9debe4b5fba28e721]

Signed-off-by: Modular Magician <magic-modules@google.com>
---
 .changelog/11316.txt                          |   3 +
 .../provider/provider_mmv1_resources.go       |   5 +-
 .../resource_scc_v2_project_mute_config.go    | 468 ++++++++++++++++++
 ...urce_scc_v2_project_mute_config_sweeper.go | 143 ++++++
 ...esource_scc_v2_project_mute_config_test.go |  75 +++
 .../scc_v2_project_mute_config.html.markdown  | 151 ++++++
 6 files changed, 843 insertions(+), 2 deletions(-)
 create mode 100644 .changelog/11316.txt
 create mode 100644 google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config.go
 create mode 100644 google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config_sweeper.go
 create mode 100644 google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config_test.go
 create mode 100644 website/docs/r/scc_v2_project_mute_config.html.markdown

diff --git a/.changelog/11316.txt b/.changelog/11316.txt
new file mode 100644
index 0000000000..16fa3b2698
--- /dev/null
+++ b/.changelog/11316.txt
@@ -0,0 +1,3 @@
+```release-note:REPLACEME
+`google_scc_v2_project_mute_config`
+```
\ No newline at end of file
diff --git a/google-beta/provider/provider_mmv1_resources.go b/google-beta/provider/provider_mmv1_resources.go
index a27883f011..a48df8d0fc 100644
--- a/google-beta/provider/provider_mmv1_resources.go
+++ b/google-beta/provider/provider_mmv1_resources.go
@@ -471,9 +471,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{
 }
 
 // Resources
-// Generated resources: 499
+// Generated resources: 500
 // Generated IAM resources: 282
-// Total generated resources: 781
+// Total generated resources: 782
 var generatedResources = map[string]*schema.Resource{
 	"google_folder_access_approval_settings":                                     accessapproval.ResourceAccessApprovalFolderSettings(),
 	"google_organization_access_approval_settings":                               accessapproval.ResourceAccessApprovalOrganizationSettings(),
@@ -1160,6 +1160,7 @@ var generatedResources = map[string]*schema.Resource{
 	"google_scc_v2_folder_mute_config":                                           securitycenterv2.ResourceSecurityCenterV2FolderMuteConfig(),
 	"google_scc_v2_organization_mute_config":                                     securitycenterv2.ResourceSecurityCenterV2OrganizationMuteConfig(),
 	"google_scc_v2_organization_notification_config":                             securitycenterv2.ResourceSecurityCenterV2OrganizationNotificationConfig(),
+	"google_scc_v2_project_mute_config":                                          securitycenterv2.ResourceSecurityCenterV2ProjectMuteConfig(),
 	"google_securityposture_posture":                                             securityposture.ResourceSecurityposturePosture(),
 	"google_securityposture_posture_deployment":                                  securityposture.ResourceSecurityposturePostureDeployment(),
 	"google_security_scanner_scan_config":                                        securityscanner.ResourceSecurityScannerScanConfig(),
diff --git a/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config.go b/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config.go
new file mode 100644
index 0000000000..ce51d44e44
--- /dev/null
+++ b/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config.go
@@ -0,0 +1,468 @@
+// 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 securitycenterv2
+
+import (
+	"fmt"
+	"log"
+	"net/http"
+	"reflect"
+	"strings"
+	"time"
+
+	"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"
+	"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"
+)
+
+func ResourceSecurityCenterV2ProjectMuteConfig() *schema.Resource {
+	return &schema.Resource{
+		Create: resourceSecurityCenterV2ProjectMuteConfigCreate,
+		Read:   resourceSecurityCenterV2ProjectMuteConfigRead,
+		Update: resourceSecurityCenterV2ProjectMuteConfigUpdate,
+		Delete: resourceSecurityCenterV2ProjectMuteConfigDelete,
+
+		Importer: &schema.ResourceImporter{
+			State: resourceSecurityCenterV2ProjectMuteConfigImport,
+		},
+
+		Timeouts: &schema.ResourceTimeout{
+			Create: schema.DefaultTimeout(20 * time.Minute),
+			Update: schema.DefaultTimeout(20 * time.Minute),
+			Delete: schema.DefaultTimeout(20 * time.Minute),
+		},
+
+		CustomizeDiff: customdiff.All(
+			tpgresource.DefaultProviderProject,
+		),
+
+		Schema: map[string]*schema.Schema{
+			"filter": {
+				Type:     schema.TypeString,
+				Required: true,
+				Description: `An expression that defines the filter to apply across create/update
+events of findings. While creating a filter string, be mindful of
+the scope in which the mute configuration is being created. E.g.,
+If a filter contains project = X but is created under the
+project = Y scope, it might not match any findings.`,
+			},
+			"mute_config_id": {
+				Type:        schema.TypeString,
+				Required:    true,
+				ForceNew:    true,
+				Description: `Unique identifier provided by the client within the parent scope.`,
+			},
+			"type": {
+				Type:        schema.TypeString,
+				Required:    true,
+				Description: `The type of the mute config.`,
+			},
+			"description": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				Description: `A description of the mute config.`,
+			},
+			"location": {
+				Type:        schema.TypeString,
+				Optional:    true,
+				ForceNew:    true,
+				Description: `location Id is provided by project. If not provided, Use global as default.`,
+				Default:     "global",
+			},
+			"create_time": {
+				Type:     schema.TypeString,
+				Computed: true,
+				Description: `The time at which the mute config was created. This field is set by
+the server and will be ignored if provided on config creation.`,
+			},
+			"most_recent_editor": {
+				Type:     schema.TypeString,
+				Computed: true,
+				Description: `Email address of the user who last edited the mute config. This
+field is set by the server and will be ignored if provided on
+config creation or update.`,
+			},
+			"name": {
+				Type:     schema.TypeString,
+				Computed: true,
+				Description: `Name of the mute config. Its format is
+projects/{project}/locations/global/muteConfigs/{configId},
+folders/{folder}/locations/global/muteConfigs/{configId},
+or organizations/{organization}/locations/global/muteConfigs/{configId}`,
+			},
+			"update_time": {
+				Type:     schema.TypeString,
+				Computed: true,
+				Description: `Output only. The most recent time at which the mute config was
+updated. This field is set by the server and will be ignored if
+provided on config creation or update.`,
+			},
+			"project": {
+				Type:     schema.TypeString,
+				Optional: true,
+				Computed: true,
+				ForceNew: true,
+			},
+		},
+		UseJSONNumber: true,
+	}
+}
+
+func resourceSecurityCenterV2ProjectMuteConfigCreate(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{})
+	descriptionProp, err := expandSecurityCenterV2ProjectMuteConfigDescription(d.Get("description"), d, config)
+	if err != nil {
+		return err
+	} else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
+		obj["description"] = descriptionProp
+	}
+	filterProp, err := expandSecurityCenterV2ProjectMuteConfigFilter(d.Get("filter"), d, config)
+	if err != nil {
+		return err
+	} else if v, ok := d.GetOkExists("filter"); !tpgresource.IsEmptyValue(reflect.ValueOf(filterProp)) && (ok || !reflect.DeepEqual(v, filterProp)) {
+		obj["filter"] = filterProp
+	}
+	typeProp, err := expandSecurityCenterV2ProjectMuteConfigType(d.Get("type"), d, config)
+	if err != nil {
+		return err
+	} else if v, ok := d.GetOkExists("type"); !tpgresource.IsEmptyValue(reflect.ValueOf(typeProp)) && (ok || !reflect.DeepEqual(v, typeProp)) {
+		obj["type"] = typeProp
+	}
+
+	url, err := tpgresource.ReplaceVars(d, config, "{{SecurityCenterV2BasePath}}projects/{{project}}/locations/{{location}}/muteConfigs?muteConfigId={{mute_config_id}}")
+	if err != nil {
+		return err
+	}
+
+	log.Printf("[DEBUG] Creating new ProjectMuteConfig: %#v", obj)
+	billingProject := ""
+
+	project, err := tpgresource.GetProject(d, config)
+	if err != nil {
+		return fmt.Errorf("Error fetching project for ProjectMuteConfig: %s", err)
+	}
+	billingProject = project
+
+	// err == nil indicates that the billing_project value was found
+	if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
+		billingProject = bp
+	}
+
+	headers := make(http.Header)
+	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),
+		Headers:   headers,
+	})
+	if err != nil {
+		return fmt.Errorf("Error creating ProjectMuteConfig: %s", err)
+	}
+	if err := d.Set("name", flattenSecurityCenterV2ProjectMuteConfigName(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, "projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}")
+	if err != nil {
+		return fmt.Errorf("Error constructing id: %s", err)
+	}
+	d.SetId(id)
+
+	log.Printf("[DEBUG] Finished creating ProjectMuteConfig %q: %#v", d.Id(), res)
+
+	return resourceSecurityCenterV2ProjectMuteConfigRead(d, meta)
+}
+
+func resourceSecurityCenterV2ProjectMuteConfigRead(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, "{{SecurityCenterV2BasePath}}projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}")
+	if err != nil {
+		return err
+	}
+
+	billingProject := ""
+
+	project, err := tpgresource.GetProject(d, config)
+	if err != nil {
+		return fmt.Errorf("Error fetching project for ProjectMuteConfig: %s", err)
+	}
+	billingProject = project
+
+	// err == nil indicates that the billing_project value was found
+	if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
+		billingProject = bp
+	}
+
+	headers := make(http.Header)
+	res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
+		Config:    config,
+		Method:    "GET",
+		Project:   billingProject,
+		RawURL:    url,
+		UserAgent: userAgent,
+		Headers:   headers,
+	})
+	if err != nil {
+		return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("SecurityCenterV2ProjectMuteConfig %q", d.Id()))
+	}
+
+	if err := d.Set("project", project); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+
+	if err := d.Set("name", flattenSecurityCenterV2ProjectMuteConfigName(res["name"], d, config)); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+	if err := d.Set("description", flattenSecurityCenterV2ProjectMuteConfigDescription(res["description"], d, config)); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+	if err := d.Set("filter", flattenSecurityCenterV2ProjectMuteConfigFilter(res["filter"], d, config)); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+	if err := d.Set("create_time", flattenSecurityCenterV2ProjectMuteConfigCreateTime(res["createTime"], d, config)); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+	if err := d.Set("update_time", flattenSecurityCenterV2ProjectMuteConfigUpdateTime(res["updateTime"], d, config)); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+	if err := d.Set("most_recent_editor", flattenSecurityCenterV2ProjectMuteConfigMostRecentEditor(res["mostRecentEditor"], d, config)); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+	if err := d.Set("type", flattenSecurityCenterV2ProjectMuteConfigType(res["type"], d, config)); err != nil {
+		return fmt.Errorf("Error reading ProjectMuteConfig: %s", err)
+	}
+
+	return nil
+}
+
+func resourceSecurityCenterV2ProjectMuteConfigUpdate(d *schema.ResourceData, meta interface{}) error {
+	config := meta.(*transport_tpg.Config)
+	userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
+	if err != nil {
+		return err
+	}
+
+	billingProject := ""
+
+	project, err := tpgresource.GetProject(d, config)
+	if err != nil {
+		return fmt.Errorf("Error fetching project for ProjectMuteConfig: %s", err)
+	}
+	billingProject = project
+
+	obj := make(map[string]interface{})
+	descriptionProp, err := expandSecurityCenterV2ProjectMuteConfigDescription(d.Get("description"), d, config)
+	if err != nil {
+		return err
+	} else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, descriptionProp)) {
+		obj["description"] = descriptionProp
+	}
+	filterProp, err := expandSecurityCenterV2ProjectMuteConfigFilter(d.Get("filter"), d, config)
+	if err != nil {
+		return err
+	} else if v, ok := d.GetOkExists("filter"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, filterProp)) {
+		obj["filter"] = filterProp
+	}
+	typeProp, err := expandSecurityCenterV2ProjectMuteConfigType(d.Get("type"), d, config)
+	if err != nil {
+		return err
+	} else if v, ok := d.GetOkExists("type"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, typeProp)) {
+		obj["type"] = typeProp
+	}
+
+	url, err := tpgresource.ReplaceVars(d, config, "{{SecurityCenterV2BasePath}}projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}")
+	if err != nil {
+		return err
+	}
+
+	log.Printf("[DEBUG] Updating ProjectMuteConfig %q: %#v", d.Id(), obj)
+	headers := make(http.Header)
+	updateMask := []string{}
+
+	if d.HasChange("description") {
+		updateMask = append(updateMask, "description")
+	}
+
+	if d.HasChange("filter") {
+		updateMask = append(updateMask, "filter")
+	}
+
+	if d.HasChange("type") {
+		updateMask = append(updateMask, "type")
+	}
+	// 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
+	}
+
+	// if updateMask is empty we are not updating anything so skip the post
+	if len(updateMask) > 0 {
+		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),
+			Headers:   headers,
+		})
+
+		if err != nil {
+			return fmt.Errorf("Error updating ProjectMuteConfig %q: %s", d.Id(), err)
+		} else {
+			log.Printf("[DEBUG] Finished updating ProjectMuteConfig %q: %#v", d.Id(), res)
+		}
+
+	}
+
+	return resourceSecurityCenterV2ProjectMuteConfigRead(d, meta)
+}
+
+func resourceSecurityCenterV2ProjectMuteConfigDelete(d *schema.ResourceData, meta interface{}) error {
+	config := meta.(*transport_tpg.Config)
+	userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
+	if err != nil {
+		return err
+	}
+
+	billingProject := ""
+
+	project, err := tpgresource.GetProject(d, config)
+	if err != nil {
+		return fmt.Errorf("Error fetching project for ProjectMuteConfig: %s", err)
+	}
+	billingProject = project
+
+	url, err := tpgresource.ReplaceVars(d, config, "{{SecurityCenterV2BasePath}}projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}")
+	if err != nil {
+		return err
+	}
+
+	var obj map[string]interface{}
+
+	// err == nil indicates that the billing_project value was found
+	if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
+		billingProject = bp
+	}
+
+	headers := make(http.Header)
+
+	log.Printf("[DEBUG] Deleting ProjectMuteConfig %q", d.Id())
+	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),
+		Headers:   headers,
+	})
+	if err != nil {
+		return transport_tpg.HandleNotFoundError(err, d, "ProjectMuteConfig")
+	}
+
+	log.Printf("[DEBUG] Finished deleting ProjectMuteConfig %q: %#v", d.Id(), res)
+	return nil
+}
+
+func resourceSecurityCenterV2ProjectMuteConfigImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) {
+	config := meta.(*transport_tpg.Config)
+	if err := tpgresource.ParseImportId([]string{
+		"^projects/(?P<project>[^/]+)/locations/(?P<location>[^/]+)/muteConfigs/(?P<mute_config_id>[^/]+)$",
+		"^(?P<project>[^/]+)/(?P<location>[^/]+)/(?P<mute_config_id>[^/]+)$",
+		"^(?P<location>[^/]+)/(?P<mute_config_id>[^/]+)$",
+	}, d, config); err != nil {
+		return nil, err
+	}
+
+	// Replace import id for the resource id
+	id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}")
+	if err != nil {
+		return nil, fmt.Errorf("Error constructing id: %s", err)
+	}
+	d.SetId(id)
+
+	return []*schema.ResourceData{d}, nil
+}
+
+func flattenSecurityCenterV2ProjectMuteConfigName(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+	return v
+}
+
+func flattenSecurityCenterV2ProjectMuteConfigDescription(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+	return v
+}
+
+func flattenSecurityCenterV2ProjectMuteConfigFilter(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+	return v
+}
+
+func flattenSecurityCenterV2ProjectMuteConfigCreateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+	return v
+}
+
+func flattenSecurityCenterV2ProjectMuteConfigUpdateTime(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+	return v
+}
+
+func flattenSecurityCenterV2ProjectMuteConfigMostRecentEditor(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+	return v
+}
+
+func flattenSecurityCenterV2ProjectMuteConfigType(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
+	return v
+}
+
+func expandSecurityCenterV2ProjectMuteConfigDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandSecurityCenterV2ProjectMuteConfigFilter(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
+	return v, nil
+}
+
+func expandSecurityCenterV2ProjectMuteConfigType(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
+	return v, nil
+}
diff --git a/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config_sweeper.go b/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config_sweeper.go
new file mode 100644
index 0000000000..5c6dc1e002
--- /dev/null
+++ b/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config_sweeper.go
@@ -0,0 +1,143 @@
+// 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 securitycenterv2
+
+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("SecurityCenterV2ProjectMuteConfig", testSweepSecurityCenterV2ProjectMuteConfig)
+}
+
+// At the time of writing, the CI only passes us-central1 as the region
+func testSweepSecurityCenterV2ProjectMuteConfig(region string) error {
+	resourceName := "SecurityCenterV2ProjectMuteConfig"
+	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/v2/projects/{{project}}/locations/{{location}}/muteConfigs", "?")[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["projectMuteConfigs"]
+	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{})
+		var name string
+		// Id detected in the delete URL, attempt to use id.
+		if obj["id"] != nil {
+			name = tpgresource.GetResourceNameFromSelfLink(obj["id"].(string))
+		} else if obj["name"] != nil {
+			name = tpgresource.GetResourceNameFromSelfLink(obj["name"].(string))
+		} else {
+			log.Printf("[INFO][SWEEPER_LOG] %s resource name and id were nil", resourceName)
+			return nil
+		}
+		// Skip resources that shouldn't be sweeped
+		if !sweeper.IsSweepableTestResource(name) {
+			nonPrefixCount++
+			continue
+		}
+
+		deleteTemplate := "https://securitycenter.googleapis.com/v2/projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}"
+		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/securitycenterv2/resource_scc_v2_project_mute_config_test.go b/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config_test.go
new file mode 100644
index 0000000000..36f0d209b1
--- /dev/null
+++ b/google-beta/services/securitycenterv2/resource_scc_v2_project_mute_config_test.go
@@ -0,0 +1,75 @@
+// Copyright (c) HashiCorp, Inc.
+// SPDX-License-Identifier: MPL-2.0
+package securitycenterv2_test
+
+import (
+	"testing"
+
+	"github.com/hashicorp/terraform-plugin-testing/helper/resource"
+	"github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest"
+	"github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar"
+)
+
+func TestAccSecurityCenterV2ProjectMuteConfig_update(t *testing.T) {
+	t.Parallel()
+
+	context := map[string]interface{}{
+		"project":       envvar.GetTestProjectFromEnv(),
+		"random_suffix": acctest.RandString(t, 10),
+	}
+
+	acctest.VcrTest(t, resource.TestCase{
+		PreCheck:                 func() { acctest.AccTestPreCheck(t) },
+		ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
+		Steps: []resource.TestStep{
+			{
+				Config: testAccSecurityCenterV2ProjectMuteConfig_basic(context),
+			},
+			{
+				ResourceName:      "google_scc_v2_project_mute_config.default",
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateVerifyIgnore: []string{
+					"project", "location", "mute_config_id",
+				},
+			},
+			{
+				Config: testAccSecurityCenterV2ProjectMuteConfig_update(context),
+			},
+			{
+				ResourceName:      "google_scc_v2_project_mute_config.default",
+				ImportState:       true,
+				ImportStateVerify: true,
+				ImportStateVerifyIgnore: []string{
+					"project", "location", "mute_config_id",
+				},
+			},
+		},
+	})
+}
+
+func testAccSecurityCenterV2ProjectMuteConfig_basic(context map[string]interface{}) string {
+	return acctest.Nprintf(`
+resource "google_scc_v2_project_mute_config" "default" {
+  mute_config_id = "tf-test-config-%{random_suffix}"
+  project   = "%{project}"
+  location       = "global"
+  description    = "A test project mute config"
+  filter         = "severity = \"HIGH\""
+  type           = "STATIC"
+}
+`, context)
+}
+
+func testAccSecurityCenterV2ProjectMuteConfig_update(context map[string]interface{}) string {
+	return acctest.Nprintf(`
+resource "google_scc_v2_project_mute_config" "default" {
+  mute_config_id = "tf-test-config-%{random_suffix}"
+  project   = "%{project}"
+  location       = "global"
+  description    = "An updated test project mute config"
+  filter         = "severity = \"HIGH\""
+  type           = "STATIC"
+}
+`, context)
+}
diff --git a/website/docs/r/scc_v2_project_mute_config.html.markdown b/website/docs/r/scc_v2_project_mute_config.html.markdown
new file mode 100644
index 0000000000..3157eea5d8
--- /dev/null
+++ b/website/docs/r/scc_v2_project_mute_config.html.markdown
@@ -0,0 +1,151 @@
+---
+# ----------------------------------------------------------------------------
+#
+#     ***     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)v2 API"
+description: |-
+  Mute Findings is a volume management feature in Security Command Center
+  that lets you manually or programmatically hide irrelevant findings,
+  and create filters to automatically silence existing and future
+  findings based on criteria you specify.
+---
+
+# google_scc_v2_project_mute_config
+
+Mute Findings is a volume management feature in Security Command Center
+that lets you manually or programmatically hide irrelevant findings,
+and create filters to automatically silence existing and future
+findings based on criteria you specify.
+
+
+To get more information about ProjectMuteConfig, see:
+
+* [API documentation](https://cloud.google.com/security-command-center/docs/reference/rest/v2/projects.muteConfigs)
+
+## Example Usage - Scc V2 Project Mute Config Basic
+
+
+```hcl
+resource "google_scc_v2_project_mute_config" "default" {
+  mute_config_id    = "my-config"
+  project = ""
+  location     = "global"
+  description  = "My custom Cloud Security Command Center Finding Project mute Configuration"
+  filter = "severity = \"HIGH\""
+  type = "STATIC"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+
+* `filter` -
+  (Required)
+  An expression that defines the filter to apply across create/update
+  events of findings. While creating a filter string, be mindful of
+  the scope in which the mute configuration is being created. E.g.,
+  If a filter contains project = X but is created under the
+  project = Y scope, it might not match any findings.
+
+* `type` -
+  (Required)
+  The type of the mute config.
+
+* `mute_config_id` -
+  (Required)
+  Unique identifier provided by the client within the parent scope.
+
+
+- - -
+
+
+* `description` -
+  (Optional)
+  A description of the mute config.
+
+* `location` -
+  (Optional)
+  location Id is provided by project. If not provided, Use global as default.
+
+* `project` - (Optional) The ID of the project in which the resource belongs.
+    If it is not provided, the provider project is used.
+
+
+## Attributes Reference
+
+In addition to the arguments listed above, the following computed attributes are exported:
+
+* `id` - an identifier for the resource with format `projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}`
+
+* `name` -
+  Name of the mute config. Its format is
+  projects/{project}/locations/global/muteConfigs/{configId},
+  folders/{folder}/locations/global/muteConfigs/{configId},
+  or organizations/{organization}/locations/global/muteConfigs/{configId}
+
+* `create_time` -
+  The time at which the mute config was created. This field is set by
+  the server and will be ignored if provided on config creation.
+
+* `update_time` -
+  Output only. The most recent time at which the mute config was
+  updated. This field is set by the server and will be ignored if
+  provided on config creation or update.
+
+* `most_recent_editor` -
+  Email address of the user who last edited the mute config. This
+  field is set by the server and will be ignored if provided on
+  config creation or update.
+
+
+## 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
+
+
+ProjectMuteConfig can be imported using any of these accepted formats:
+
+* `projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}`
+* `{{project}}/{{location}}/{{mute_config_id}}`
+* `{{location}}/{{mute_config_id}}`
+
+
+In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import ProjectMuteConfig using one of the formats above. For example:
+
+```tf
+import {
+  id = "projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}"
+  to = google_scc_v2_project_mute_config.default
+}
+```
+
+When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), ProjectMuteConfig can be imported using one of the formats above. For example:
+
+```
+$ terraform import google_scc_v2_project_mute_config.default projects/{{project}}/locations/{{location}}/muteConfigs/{{mute_config_id}}
+$ terraform import google_scc_v2_project_mute_config.default {{project}}/{{location}}/{{mute_config_id}}
+$ terraform import google_scc_v2_project_mute_config.default {{location}}/{{mute_config_id}}
+```
+
+## User Project Overrides
+
+This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).