From 3c5f9f88889ecef53361b00fcb282eec4f8e3ba0 Mon Sep 17 00:00:00 2001
From: shuting <shutting06@gmail.com>
Date: Mon, 21 Dec 2020 11:04:19 -0800
Subject: [PATCH] 1398 - Reduce RCR throttling requests (#1406)

* reduce RCR throttling requests by merging policy application (policy - namespace) results into single RCR

* - refactor policy controller; - fix RCR issue

* - refactor RCR controller; - fix cpolr on ns update; - reduce throttling when getting resources; - fix tests

* update CRD schema

* fix typo
---
 charts/kyverno/crds/crds.yaml                 |  40 +++--
 cmd/initContainer/main.go                     |   6 +-
 .../crds/kyverno.io_clusterpolicies.yaml      |  19 ++-
 ...yverno.io_clusterreportchangerequests.yaml |   2 +
 definitions/crds/kyverno.io_policies.yaml     |  19 ++-
 definitions/install.yaml                      |  40 +++--
 definitions/install_debug.yaml                |  40 +++--
 .../clusterreportchangerequest_types.go       |   4 +-
 pkg/config/dynamicconfig.go                   |   7 +
 pkg/engine/response/response.go               |  18 +-
 pkg/generate/generate.go                      |   2 +-
 pkg/kyverno/apply/report.go                   |  53 +++---
 pkg/kyverno/apply/report_test.go              |   5 +
 pkg/policy/apply.go                           |  11 +-
 pkg/policy/common.go                          |  15 +-
 pkg/policy/existing.go                        | 134 +++++++++------
 pkg/policy/report.go                          |  41 +++--
 pkg/policy/validate_controller.go             | 111 +++++++------
 pkg/policyreport/builder.go                   | 156 +++++++++++-------
 pkg/policyreport/policyreport.go              |  49 ++++--
 pkg/policyreport/reportcontroller.go          |  47 ++++--
 pkg/policyreport/reportrequest.go             | 101 +++++++-----
 pkg/webhooks/validation.go                    |  27 +--
 23 files changed, 583 insertions(+), 364 deletions(-)

diff --git a/charts/kyverno/crds/crds.yaml b/charts/kyverno/crds/crds.yaml
index 8889ec1e07..fd643821c9 100644
--- a/charts/kyverno/crds/crds.yaml
+++ b/charts/kyverno/crds/crds.yaml
@@ -224,9 +224,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -236,9 +237,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -251,7 +253,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
@@ -959,6 +964,8 @@ spec:
     kind: ClusterReportChangeRequest
     listKind: ClusterReportChangeRequestList
     plural: clusterreportchangerequests
+    shortNames:
+    - crcr
     singular: clusterreportchangerequest
   scope: Cluster
   versions:
@@ -1688,9 +1695,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -1700,9 +1708,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -1715,7 +1724,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
diff --git a/cmd/initContainer/main.go b/cmd/initContainer/main.go
index 52bc2e1921..515df65d47 100644
--- a/cmd/initContainer/main.go
+++ b/cmd/initContainer/main.go
@@ -308,12 +308,10 @@ func removeReportChangeRequest(client *client.Client, kind string) error {
 		return nil
 	}
 
-	var wg sync.WaitGroup
-	wg.Add(len(rcrList.Items))
 	for _, rcr := range rcrList.Items {
-		go deleteResource(client, rcr.GetAPIVersion(), rcr.GetKind(), rcr.GetNamespace(), rcr.GetName(), &wg)
+		deleteResource(client, rcr.GetAPIVersion(), rcr.GetKind(), rcr.GetNamespace(), rcr.GetName(), nil)
 	}
-	wg.Wait()
+
 	return nil
 }
 
diff --git a/definitions/crds/kyverno.io_clusterpolicies.yaml b/definitions/crds/kyverno.io_clusterpolicies.yaml
index ad765fd659..853ad21d58 100644
--- a/definitions/crds/kyverno.io_clusterpolicies.yaml
+++ b/definitions/crds/kyverno.io_clusterpolicies.yaml
@@ -226,9 +226,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -238,9 +239,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -253,7 +255,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
diff --git a/definitions/crds/kyverno.io_clusterreportchangerequests.yaml b/definitions/crds/kyverno.io_clusterreportchangerequests.yaml
index ff5e85728a..a841f2a1d7 100644
--- a/definitions/crds/kyverno.io_clusterreportchangerequests.yaml
+++ b/definitions/crds/kyverno.io_clusterreportchangerequests.yaml
@@ -13,6 +13,8 @@ spec:
     kind: ClusterReportChangeRequest
     listKind: ClusterReportChangeRequestList
     plural: clusterreportchangerequests
+    shortNames:
+    - crcr
     singular: clusterreportchangerequest
   scope: Cluster
   versions:
diff --git a/definitions/crds/kyverno.io_policies.yaml b/definitions/crds/kyverno.io_policies.yaml
index cbdd38b8bc..ab0948973b 100644
--- a/definitions/crds/kyverno.io_policies.yaml
+++ b/definitions/crds/kyverno.io_policies.yaml
@@ -227,9 +227,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -239,9 +240,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -254,7 +256,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
diff --git a/definitions/install.yaml b/definitions/install.yaml
index 1a77ea4be9..19e90cdd22 100644
--- a/definitions/install.yaml
+++ b/definitions/install.yaml
@@ -229,9 +229,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -241,9 +242,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -256,7 +258,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
@@ -964,6 +969,8 @@ spec:
     kind: ClusterReportChangeRequest
     listKind: ClusterReportChangeRequestList
     plural: clusterreportchangerequests
+    shortNames:
+    - crcr
     singular: clusterreportchangerequest
   scope: Cluster
   versions:
@@ -1693,9 +1700,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -1705,9 +1713,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -1720,7 +1729,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
diff --git a/definitions/install_debug.yaml b/definitions/install_debug.yaml
index 690c475a9a..9be761e691 100755
--- a/definitions/install_debug.yaml
+++ b/definitions/install_debug.yaml
@@ -229,9 +229,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -241,9 +242,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -256,7 +258,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
@@ -964,6 +969,8 @@ spec:
     kind: ClusterReportChangeRequest
     listKind: ClusterReportChangeRequestList
     plural: clusterreportchangerequests
+    shortNames:
+    - crcr
     singular: clusterreportchangerequest
   scope: Cluster
   versions:
@@ -1693,9 +1700,10 @@ spec:
                           description: APIVersion specifies resource apiVersion.
                           type: string
                         clone:
-                          description: Clone specified the source resource used to
-                            populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Clone specifies the source resource used to
+                            populate each generated resource. At most one of Data
+                            or Clone can be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           properties:
                             name:
                               description: Name specifies name of the resource.
@@ -1705,9 +1713,10 @@ spec:
                               type: string
                           type: object
                         data:
-                          description: Data provides the resource manifest to used
-                            to populate each generated resource. Exactly one of Data
-                            or Clone must be specified.
+                          description: Data provides the resource declaration used
+                            to populate each generated resource. At most one of Data
+                            or Clone must be specified. If neither are provided, the
+                            generated resource will be created with default data only.
                           x-kubernetes-preserve-unknown-fields: true
                         kind:
                           description: Kind specifies resource kind.
@@ -1720,7 +1729,10 @@ spec:
                           type: string
                         synchronize:
                           description: Synchronize controls if generated resources
-                            should be kept in-sync with their source resource. Optional.
+                            should be kept in-sync with their source resource. If
+                            Synchronize is set to "true" changes to generated resources
+                            will be overwritten with resource data from Data or the
+                            resource specified in the Clone declaration. Optional.
                             Defaults to "false" if not specified.
                           type: boolean
                       type: object
diff --git a/pkg/api/kyverno/v1alpha1/clusterreportchangerequest_types.go b/pkg/api/kyverno/v1alpha1/clusterreportchangerequest_types.go
index 4234cfe2a3..7f9350839a 100644
--- a/pkg/api/kyverno/v1alpha1/clusterreportchangerequest_types.go
+++ b/pkg/api/kyverno/v1alpha1/clusterreportchangerequest_types.go
@@ -30,7 +30,7 @@ import (
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
 // +genclient:nonNamespaced
 // +kubebuilder:object:root=true
-// +kubebuilder:resource:path=clusterreportchangerequests
+// +kubebuilder:resource:path=clusterreportchangerequests,scope="Cluster",shortName=crcr
 // +kubebuilder:printcolumn:name="Kind",type=string,JSONPath=`.scope.kind`,priority=1
 // +kubebuilder:printcolumn:name="Name",type=string,JSONPath=`.scope.name`,priority=1
 // +kubebuilder:printcolumn:name="Pass",type=integer,JSONPath=`.summary.pass`
@@ -39,8 +39,6 @@ import (
 // +kubebuilder:printcolumn:name="Error",type=integer,JSONPath=`.summary.error`
 // +kubebuilder:printcolumn:name="Skip",type=integer,JSONPath=`.summary.skip`
 // +kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp"
-// +kubebuilder:resource:shortName=crcr
-// +kubebuilder:resource:scope=Cluster
 type ClusterReportChangeRequest struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
diff --git a/pkg/config/dynamicconfig.go b/pkg/config/dynamicconfig.go
index 0177b5e2b6..ada7a4b377 100644
--- a/pkg/config/dynamicconfig.go
+++ b/pkg/config/dynamicconfig.go
@@ -42,6 +42,13 @@ func (cd *ConfigData) ToFilter(kind, namespace, name string) bool {
 		if wildcard.Match(f.Kind, kind) && wildcard.Match(f.Namespace, namespace) && wildcard.Match(f.Name, name) {
 			return true
 		}
+
+		if kind == "Namespace" {
+			// [Namespace,kube-system,*] || [*,kube-system,*]
+			if (f.Kind == "Namespace" || f.Kind == "*") && wildcard.Match(f.Namespace, name) {
+				return true
+			}
+		}
 	}
 	return false
 }
diff --git a/pkg/engine/response/response.go b/pkg/engine/response/response.go
index eeb37dd2cb..4b747c1be5 100644
--- a/pkg/engine/response/response.go
+++ b/pkg/engine/response/response.go
@@ -31,11 +31,14 @@ type PolicyResponse struct {
 
 //ResourceSpec resource action applied on
 type ResourceSpec struct {
-	//TODO: support ApiVersion
 	Kind       string `json:"kind"`
 	APIVersion string `json:"apiVersion"`
 	Namespace  string `json:"namespace"`
 	Name       string `json:"name"`
+
+	// UID is not used to build the unique identifier
+	// optional
+	UID string `json:"uid"`
 }
 
 //GetKey returns the key
@@ -51,7 +54,7 @@ type PolicyStats struct {
 	RulesAppliedCount int `json:"rulesAppliedCount"`
 }
 
-//RuleResponse details for each rule applicatino
+//RuleResponse details for each rule application
 type RuleResponse struct {
 	// rule name specified in policy
 	Name string `json:"name"`
@@ -110,6 +113,17 @@ func (er EngineResponse) GetSuccessRules() []string {
 	return er.getRules(true)
 }
 
+// GetResourceSpec returns resourceSpec of er
+func (er EngineResponse) GetResourceSpec() ResourceSpec {
+	return ResourceSpec{
+		Kind:       er.PatchedResource.GetKind(),
+		APIVersion: er.PatchedResource.GetAPIVersion(),
+		Namespace:  er.PatchedResource.GetNamespace(),
+		Name:       er.PatchedResource.GetName(),
+		UID:        string(er.PatchedResource.GetUID()),
+	}
+}
+
 func (er EngineResponse) getRules(success bool) []string {
 	var rules []string
 	for _, r := range er.PolicyResponse.Rules {
diff --git a/pkg/generate/generate.go b/pkg/generate/generate.go
index 49f233297c..f2e739c1a3 100644
--- a/pkg/generate/generate.go
+++ b/pkg/generate/generate.go
@@ -32,7 +32,7 @@ func (c *Controller) processGR(gr *kyverno.GenerateRequest) error {
 	// 1 - Check if the resource exists
 	resource, err = getResource(c.client, gr.Spec.Resource)
 	if err != nil {
-		// Dont update status
+		// Don't update status
 		logger.V(3).Info("resource does not exist or is pending creation, re-queueing", "details", err.Error())
 		return err
 	}
diff --git a/pkg/kyverno/apply/report.go b/pkg/kyverno/apply/report.go
index 51cd0b6f4e..75c3415e5c 100644
--- a/pkg/kyverno/apply/report.go
+++ b/pkg/kyverno/apply/report.go
@@ -7,11 +7,13 @@ import (
 
 	report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
 	"github.com/kyverno/kyverno/pkg/engine/response"
+	"github.com/kyverno/kyverno/pkg/engine/utils"
 	engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
 	"github.com/kyverno/kyverno/pkg/policyreport"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/types"
 	log "sigs.k8s.io/controller-runtime/pkg/log"
 )
 
@@ -42,7 +44,7 @@ func buildPolicyReports(resps []response.EngineResponse, skippedPolicies []Skipp
 			}
 
 			if raw, err = json.Marshal(report); err != nil {
-				log.Log.V(3).Info("failed to serilize policy report", "error", err)
+				log.Log.V(3).Info("failed to serialize policy report", "error", err)
 				continue
 			}
 
@@ -70,7 +72,7 @@ func buildPolicyReports(resps []response.EngineResponse, skippedPolicies []Skipp
 
 			report.SetName(scope)
 			if raw, err = json.Marshal(report); err != nil {
-				log.Log.V(3).Info("failed to serilize policy report", "name", report.Name, "scope", scope, "error", err)
+				log.Log.V(3).Info("failed to serialize policy report", "name", report.Name, "scope", scope, "error", err)
 			}
 		} else {
 			report := &report.PolicyReport{
@@ -87,7 +89,7 @@ func buildPolicyReports(resps []response.EngineResponse, skippedPolicies []Skipp
 			report.SetNamespace(ns)
 
 			if raw, err = json.Marshal(report); err != nil {
-				log.Log.V(3).Info("failed to serilize policy report", "name", report.Name, "scope", scope, "error", err)
+				log.Log.V(3).Info("failed to serialize policy report", "name", report.Name, "scope", scope, "error", err)
 			}
 		}
 
@@ -111,33 +113,38 @@ func buildPolicyResults(resps []response.EngineResponse) map[string][]*report.Po
 
 	for _, info := range infos {
 		var appname string
-
-		ns := info.Resource.GetNamespace()
+		ns := info.Namespace
 		if ns != "" {
 			appname = fmt.Sprintf("policyreport-ns-%s", ns)
 		} else {
 			appname = fmt.Sprintf(clusterpolicyreport)
 		}
 
-		for _, rule := range info.Rules {
-			result := report.PolicyReportResult{
-				Policy: info.PolicyName,
-				Resources: []*corev1.ObjectReference{
-					{
-						Kind:       info.Resource.GetKind(),
-						Namespace:  info.Resource.GetNamespace(),
-						APIVersion: info.Resource.GetAPIVersion(),
-						Name:       info.Resource.GetName(),
-						UID:        info.Resource.GetUID(),
-					},
-				},
-				Scored: true,
-			}
+		for _, infoResult := range info.Results {
+			for _, rule := range infoResult.Rules {
+				if rule.Type != utils.Validation.String() {
+					continue
+				}
 
-			result.Rule = rule.Name
-			result.Message = rule.Message
-			result.Status = report.PolicyStatus(rule.Check)
-			results[appname] = append(results[appname], &result)
+				result := report.PolicyReportResult{
+					Policy: info.PolicyName,
+					Resources: []*corev1.ObjectReference{
+						{
+							Kind:       infoResult.Resource.Kind,
+							Namespace:  infoResult.Resource.Namespace,
+							APIVersion: infoResult.Resource.APIVersion,
+							Name:       infoResult.Resource.Name,
+							UID:        types.UID(infoResult.Resource.UID),
+						},
+					},
+					Scored: true,
+				}
+
+				result.Rule = rule.Name
+				result.Message = rule.Message
+				result.Status = report.PolicyStatus(rule.Check)
+				results[appname] = append(results[appname], &result)
+			}
 		}
 	}
 
diff --git a/pkg/kyverno/apply/report_test.go b/pkg/kyverno/apply/report_test.go
index a82676ee45..5e357adfc1 100644
--- a/pkg/kyverno/apply/report_test.go
+++ b/pkg/kyverno/apply/report_test.go
@@ -8,6 +8,7 @@ import (
 	report "github.com/kyverno/kyverno/pkg/api/policyreport/v1alpha1"
 	"github.com/kyverno/kyverno/pkg/common"
 	"github.com/kyverno/kyverno/pkg/engine/response"
+	"github.com/kyverno/kyverno/pkg/engine/utils"
 	"gotest.tools/assert"
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@@ -30,10 +31,12 @@ var engineResponses = []response.EngineResponse{
 			Rules: []response.RuleResponse{
 				{
 					Name:    "policy1-rule1",
+					Type:    utils.Validation.String(),
 					Success: true,
 				},
 				{
 					Name:    "policy1-rule2",
+					Type:    utils.Validation.String(),
 					Success: false,
 				},
 			},
@@ -54,10 +57,12 @@ var engineResponses = []response.EngineResponse{
 			Rules: []response.RuleResponse{
 				{
 					Name:    "clusterpolicy2-rule1",
+					Type:    utils.Validation.String(),
 					Success: true,
 				},
 				{
 					Name:    "clusterpolicy2-rule2",
+					Type:    utils.Validation.String(),
 					Success: false,
 				},
 			},
diff --git a/pkg/policy/apply.go b/pkg/policy/apply.go
index 2b2f9a50a4..5281b2295a 100644
--- a/pkg/policy/apply.go
+++ b/pkg/policy/apply.go
@@ -19,7 +19,6 @@ import (
 )
 
 // applyPolicy applies policy on a resource
-//TODO: generation rules
 func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructured, logger logr.Logger, excludeGroupRole []string, resCache resourcecache.ResourceCacheIface) (responses []response.EngineResponse) {
 	startTime := time.Now()
 	defer func() {
@@ -35,23 +34,21 @@ func applyPolicy(policy kyverno.ClusterPolicy, resource unstructured.Unstructure
 	var engineResponses []response.EngineResponse
 	var engineResponseMutation, engineResponseValidation response.EngineResponse
 	var err error
-	// build context
+
 	ctx := context.NewContext()
 	err = ctx.AddResource(transformResource(resource))
 	if err != nil {
 		logger.Error(err, "enable to add transform resource to ctx")
 	}
-	//MUTATION
+
 	engineResponseMutation, err = mutation(policy, resource, ctx, logger, resCache, ctx)
 	if err != nil {
 		logger.Error(err, "failed to process mutation rule")
 	}
 
-	//VALIDATION
 	engineResponseValidation = engine.Validate(engine.PolicyContext{Policy: policy, Context: ctx, NewResource: resource, ExcludeGroupRole: excludeGroupRole, ResourceCache: resCache, JSONContext: ctx})
 	engineResponses = append(engineResponses, mergeRuleRespose(engineResponseMutation, engineResponseValidation))
 
-	//TODO: GENERATION
 	return engineResponses
 }
 
@@ -62,10 +59,10 @@ func mutation(policy kyverno.ClusterPolicy, resource unstructured.Unstructured,
 		log.V(4).Info("failed to apply mutation rules; reporting them")
 		return engineResponse, nil
 	}
-	// Verify if the JSON pathes returned by the Mutate are already applied to the resource
+	// Verify if the JSON patches returned by the Mutate are already applied to the resource
 	if reflect.DeepEqual(resource, engineResponse.PatchedResource) {
 		// resources matches
-		log.V(4).Info("resource already satisfys the policy")
+		log.V(4).Info("resource already satisfies the policy")
 		return engineResponse, nil
 	}
 	return getFailedOverallRuleInfo(resource, engineResponse, log)
diff --git a/pkg/policy/common.go b/pkg/policy/common.go
index 2b5c83dca6..ba9d2fae1c 100644
--- a/pkg/policy/common.go
+++ b/pkg/policy/common.go
@@ -8,7 +8,6 @@ import (
 	"github.com/go-logr/logr"
 	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
 	"github.com/kyverno/kyverno/pkg/config"
-	client "github.com/kyverno/kyverno/pkg/dclient"
 	"github.com/kyverno/kyverno/pkg/utils"
 	"github.com/minio/minio/pkg/wildcard"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -92,7 +91,7 @@ func ExcludePod(resourceMap map[string]unstructured.Unstructured, log logr.Logge
 	return resourceMap
 }
 
-// GetNamespacesForRule gets the matched namespacse list for the given rule
+// GetNamespacesForRule gets the matched namespaces list for the given rule
 func GetNamespacesForRule(rule *kyverno.Rule, nslister listerv1.NamespaceLister, log logr.Logger) []string {
 	if len(rule.MatchResources.Namespaces) == 0 {
 		return GetAllNamespaces(nslister, log)
@@ -160,7 +159,7 @@ func GetAllNamespaces(nslister listerv1.NamespaceLister, log logr.Logger) []stri
 }
 
 // GetResourcesPerNamespace ...
-func GetResourcesPerNamespace(kind string, client *client.Client, namespace string, rule kyverno.Rule, configHandler config.Interface, log logr.Logger) map[string]unstructured.Unstructured {
+func (pc *PolicyController) getResourcesPerNamespace(kind string, namespace string, rule kyverno.Rule, log logr.Logger) map[string]unstructured.Unstructured {
 	resourceMap := map[string]unstructured.Unstructured{}
 	ls := rule.MatchResources.Selector
 
@@ -168,7 +167,7 @@ func GetResourcesPerNamespace(kind string, client *client.Client, namespace stri
 		namespace = ""
 	}
 
-	list, err := client.ListResource("", kind, namespace, ls)
+	list, err := pc.client.ListResource("", kind, namespace, ls)
 	if err != nil {
 		log.Error(err, "failed to list resources", "kind", kind, "namespace", namespace)
 		return nil
@@ -192,7 +191,7 @@ func GetResourcesPerNamespace(kind string, client *client.Client, namespace stri
 			}
 		}
 		// Skip the filtered resources
-		if configHandler.ToFilter(r.GetKind(), r.GetNamespace(), r.GetName()) {
+		if pc.configHandler.ToFilter(r.GetKind(), r.GetNamespace(), r.GetName()) {
 			continue
 		}
 
@@ -202,12 +201,12 @@ func GetResourcesPerNamespace(kind string, client *client.Client, namespace stri
 
 	// exclude the resources
 	// skip resources to be filtered
-	ExcludeResources(resourceMap, rule.ExcludeResources.ResourceDescription, configHandler, log)
+	excludeResources(resourceMap, rule.ExcludeResources.ResourceDescription, pc.configHandler, log)
 	return resourceMap
 }
 
 // ExcludeResources ...
-func ExcludeResources(included map[string]unstructured.Unstructured, exclude kyverno.ResourceDescription, configHandler config.Interface, log logr.Logger) {
+func excludeResources(included map[string]unstructured.Unstructured, exclude kyverno.ResourceDescription, configHandler config.Interface, log logr.Logger) {
 	if reflect.DeepEqual(exclude, (kyverno.ResourceDescription{})) {
 		return
 	}
@@ -270,7 +269,7 @@ func ExcludeResources(included map[string]unstructured.Unstructured, exclude kyv
 
 	// check exclude condition for each resource
 	for uid, resource := range included {
-		// 0 -> dont check
+		// 0 -> don't check
 		// 1 -> is not to be exclude
 		// 2 -> to be exclude
 		excludeEval := []Condition{}
diff --git a/pkg/policy/existing.go b/pkg/policy/existing.go
index a4aab74fd1..4437208025 100644
--- a/pkg/policy/existing.go
+++ b/pkg/policy/existing.go
@@ -1,6 +1,7 @@
 package policy
 
 import (
+	"errors"
 	"sync"
 	"time"
 
@@ -11,72 +12,85 @@ import (
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 )
 
-func (pc *PolicyController) processExistingResources(policy *kyverno.ClusterPolicy) []response.EngineResponse {
+func (pc *PolicyController) processExistingResources(policy *kyverno.ClusterPolicy) {
 	logger := pc.log.WithValues("policy", policy.Name)
-	// Parse through all the resources
-	// drops the cache after configured rebuild time
+	logger.V(4).Info("applying policy to existing resources")
+
+	// Parse through all the resources drops the cache after configured rebuild time
 	pc.rm.Drop()
-	var engineResponses []response.EngineResponse
-	// get resource that are satisfy the resource description defined in the rules
-	resourceMap := pc.listResources(policy)
-	for _, resource := range resourceMap {
-		// pre-processing, check if the policy and resource version has been processed before
-		if !pc.rm.ProcessResource(policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion()) {
-			logger.V(4).Info("policy and resource already processed", "policyResourceVersion", policy.ResourceVersion, "resourceResourceVersion", resource.GetResourceVersion(), "kind", resource.GetKind(), "namespace", resource.GetNamespace(), "name", resource.GetName())
+
+	for _, rule := range policy.Spec.Rules {
+		if !rule.HasValidate() {
 			continue
 		}
 
-		// apply the policy on each
-		engineResponse := applyPolicy(*policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.resCache)
-		// get engine response for mutation & validation independently
-		engineResponses = append(engineResponses, engineResponse...)
-		// post-processing, register the resource as processed
-		pc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
-	}
-	return engineResponses
-}
-
-func (pc *PolicyController) listResources(policy *kyverno.ClusterPolicy) map[string]unstructured.Unstructured {
-	pc.log.V(4).Info("list resources to be processed")
-
-	// key uid
-	resourceMap := map[string]unstructured.Unstructured{}
-
-	for _, rule := range policy.Spec.Rules {
 		for _, k := range rule.MatchResources.Kinds {
-
-			resourceSchema, _, err := pc.client.DiscoveryClient.FindResource("", k)
+			logger = logger.WithValues("rule", rule.Name, "kind", k)
+			namespaced, err := pc.rm.GetScope(k)
 			if err != nil {
-				pc.log.Error(err, "failed to find resource", "kind", k)
+				resourceSchema, _, err := pc.client.DiscoveryClient.FindResource("", k)
+				if err != nil {
+					logger.Error(err, "failed to find resource", "kind", k)
+					continue
+				}
+
+				namespaced = resourceSchema.Namespaced
+				pc.rm.RegisterScope(k, namespaced)
+			}
+
+			if !namespaced {
+				pc.applyAndReportPerNamespace(policy, k, "", rule, logger)
 				continue
 			}
 
-			if !resourceSchema.Namespaced {
-				rMap := GetResourcesPerNamespace(k, pc.client, "", rule, pc.configHandler, pc.log)
-				MergeResources(resourceMap, rMap)
-			} else {
-				namespaces := GetNamespacesForRule(&rule, pc.nsLister, pc.log)
-				for _, ns := range namespaces {
-					rMap := GetResourcesPerNamespace(k, pc.client, ns, rule, pc.configHandler, pc.log)
-					MergeResources(resourceMap, rMap)
-				}
+			namespaces := GetNamespacesForRule(&rule, pc.nsLister, logger)
+			for _, ns := range namespaces {
+				pc.applyAndReportPerNamespace(policy, k, ns, rule, logger)
 			}
 		}
 	}
+}
 
-	return excludeAutoGenResources(*policy, resourceMap, pc.log)
+func (pc *PolicyController) applyAndReportPerNamespace(policy *kyverno.ClusterPolicy, kind string, ns string, rule kyverno.Rule, logger logr.Logger) {
+	rMap := pc.getResourcesPerNamespace(kind, ns, rule, logger)
+	excludeAutoGenResources(*policy, rMap, logger)
+
+	if len(rMap) == 0 {
+		return
+	}
+
+	var engineResponses []response.EngineResponse
+	for _, resource := range rMap {
+		responses := pc.applyPolicy(policy, resource, logger)
+		engineResponses = append(engineResponses, responses...)
+	}
+
+	pc.report(policy.Name, engineResponses, logger)
+}
+
+func (pc *PolicyController) applyPolicy(policy *kyverno.ClusterPolicy, resource unstructured.Unstructured, logger logr.Logger) (engineResponses []response.EngineResponse) {
+	// pre-processing, check if the policy and resource version has been processed before
+	if !pc.rm.ProcessResource(policy.Name, policy.ResourceVersion, resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion()) {
+		logger.V(4).Info("policy and resource already processed", "policyResourceVersion", policy.ResourceVersion, "resourceResourceVersion", resource.GetResourceVersion(), "kind", resource.GetKind(), "namespace", resource.GetNamespace(), "name", resource.GetName())
+	}
+
+	engineResponse := applyPolicy(*policy, resource, logger, pc.configHandler.GetExcludeGroupRole(), pc.resCache)
+	engineResponses = append(engineResponses, engineResponse...)
+
+	// post-processing, register the resource as processed
+	pc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), resource.GetKind(), resource.GetNamespace(), resource.GetName(), resource.GetResourceVersion())
+
+	return
 }
 
 // excludeAutoGenResources filter out the pods / jobs with ownerReference
-func excludeAutoGenResources(policy kyverno.ClusterPolicy, resourceMap map[string]unstructured.Unstructured, log logr.Logger) map[string]unstructured.Unstructured {
+func excludeAutoGenResources(policy kyverno.ClusterPolicy, resourceMap map[string]unstructured.Unstructured, log logr.Logger) {
 	for uid, r := range resourceMap {
 		if engine.SkipPolicyApplication(policy, r) {
 			log.V(4).Info("exclude resource", "namespace", r.GetNamespace(), "kind", r.GetKind(), "name", r.GetName())
 			delete(resourceMap, uid)
 		}
 	}
-
-	return resourceMap
 }
 
 //Condition defines condition type
@@ -91,16 +105,10 @@ const (
 	Skip Condition = 2
 )
 
-// merge b into a map
-func mergeResources(a, b map[string]unstructured.Unstructured) {
-	for k, v := range b {
-		a[k] = v
-	}
-}
-
 //NewResourceManager returns a new ResourceManager
 func NewResourceManager(rebuildTime int64) *ResourceManager {
 	rm := ResourceManager{
+		scope:       make(map[string]bool),
 		data:        make(map[string]interface{}),
 		time:        time.Now(),
 		rebuildTime: rebuildTime,
@@ -113,6 +121,7 @@ func NewResourceManager(rebuildTime int64) *ResourceManager {
 type ResourceManager struct {
 	// we drop and re-build the cache
 	// based on the memory consumer of by the map
+	scope       map[string]bool
 	data        map[string]interface{}
 	mux         sync.RWMutex
 	time        time.Time
@@ -123,7 +132,8 @@ type resourceManager interface {
 	ProcessResource(policy, pv, kind, ns, name, rv string) bool
 	//TODO	removeResource(kind, ns, name string) error
 	RegisterResource(policy, pv, kind, ns, name, rv string)
-	// reload
+	RegisterScope(kind string, namespaced bool)
+	GetScope(kind string) (bool, error)
 	Drop()
 }
 
@@ -160,6 +170,28 @@ func (rm *ResourceManager) ProcessResource(policy, pv, kind, ns, name, rv string
 	return !ok
 }
 
+// RegisterScope stores the scope of the given kind
+func (rm *ResourceManager) RegisterScope(kind string, namespaced bool) {
+	rm.mux.Lock()
+	defer rm.mux.Unlock()
+
+	rm.scope[kind] = namespaced
+}
+
+// GetScope gets the scope of the given kind
+// return error if kind is not registered
+func (rm *ResourceManager) GetScope(kind string) (bool, error) {
+	rm.mux.RLock()
+	defer rm.mux.RUnlock()
+
+	namespaced, ok := rm.scope[kind]
+	if !ok {
+		return false, errors.New("NotFound")
+	}
+
+	return namespaced, nil
+}
+
 func buildKey(policy, pv, kind, ns, name, rv string) string {
 	return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv
 }
diff --git a/pkg/policy/report.go b/pkg/policy/report.go
index ed5ef74ba6..1680511506 100644
--- a/pkg/policy/report.go
+++ b/pkg/policy/report.go
@@ -9,21 +9,17 @@ import (
 	"github.com/kyverno/kyverno/pkg/policyreport"
 )
 
-// for each policy-resource response
-// - has violation -> report
-// - no violation -> cleanup policy violations
-func (pc *PolicyController) cleanupAndReport(engineResponses []response.EngineResponse) {
-	logger := pc.log
-	// generate Events
-	eventInfos := generateEvents(pc.log, engineResponses)
+func (pc *PolicyController) report(policy string, engineResponses []response.EngineResponse, logger logr.Logger) {
+	eventInfos := generateEvents(logger, engineResponses)
 	pc.eventGen.Add(eventInfos...)
-	// create policy violation
-	pvInfos := policyreport.GeneratePRsFromEngineResponse(engineResponses, logger)
-	for i := range pvInfos {
-		pvInfos[i].FromSync = true
-	}
 
-	pc.prGenerator.Add(pvInfos...)
+	pvInfos := policyreport.GeneratePRsFromEngineResponse(engineResponses, logger)
+
+	// as engineResponses holds the results for all matched resources in one namespace
+	// we can merge pvInfos into a single object to reduce update frequency (throttling request) on RCR
+	info := mergePvInfos(pvInfos)
+	pc.prGenerator.Add(info)
+	logger.V(4).Info("added a request to RCR generator", "key", info.ToKey())
 }
 
 func generateEvents(log logr.Logger, ers []response.EngineResponse) []event.Info {
@@ -61,3 +57,22 @@ func generateEventsPerEr(log logr.Logger, er response.EngineResponse) []event.In
 
 	return eventInfos
 }
+
+func mergePvInfos(infos []policyreport.Info) policyreport.Info {
+	aggregatedInfo := policyreport.Info{}
+	if len(infos) == 0 {
+		return aggregatedInfo
+	}
+
+	var results []policyreport.EngineResponseResult
+	for _, info := range infos {
+		for _, res := range info.Results {
+			results = append(results, res)
+		}
+	}
+
+	aggregatedInfo.PolicyName = infos[0].PolicyName
+	aggregatedInfo.Namespace = infos[0].Namespace
+	aggregatedInfo.Results = results
+	return aggregatedInfo
+}
diff --git a/pkg/policy/validate_controller.go b/pkg/policy/validate_controller.go
index 25091b0e61..c12e943e50 100644
--- a/pkg/policy/validate_controller.go
+++ b/pkg/policy/validate_controller.go
@@ -50,7 +50,7 @@ type PolicyController struct {
 	eventGen      event.Interface
 	eventRecorder record.EventRecorder
 
-	// Policys that need to be synced
+	// Policies that need to be synced
 	queue workqueue.RateLimitingInterface
 
 	// pLister can list/get policy from the shared informer's store
@@ -62,7 +62,7 @@ type PolicyController struct {
 	// grLister can list/get generate request from the shared informer's store
 	grLister kyvernolister.GenerateRequestLister
 
-	// nsLister can list/get namespacecs from the shared informer's store
+	// nsLister can list/get namespaces from the shared informer's store
 	nsLister listerv1.NamespaceLister
 
 	// pListerSynced returns true if the cluster policy store has been synced at least once
@@ -82,6 +82,7 @@ type PolicyController struct {
 
 	// grListerSynced returns true if the generate request store has been synced at least once
 	grListerSynced cache.InformerSynced
+
 	// Resource manager, manages the mapping for already processed resource
 	rm resourceManager
 
@@ -203,7 +204,7 @@ func (pc *PolicyController) updatePolicy(old, cur interface{}) {
 
 	logger.V(4).Info("updating policy", "name", oldP.Name)
 
-	pc.enqueueDeletedRule(oldP, curP)
+	pc.enqueueRCRDeletedRule(oldP, curP)
 	pc.enqueuePolicy(curP)
 }
 
@@ -213,7 +214,7 @@ func (pc *PolicyController) deletePolicy(obj interface{}) {
 	if !ok {
 		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
 		if !ok {
-			logger.Info("couldnt get object from tomstone", "obj", obj)
+			logger.Info("couldn't get object from tombstone", "obj", obj)
 			return
 		}
 
@@ -226,9 +227,10 @@ func (pc *PolicyController) deletePolicy(obj interface{}) {
 
 	logger.V(4).Info("deleting policy", "name", p.Name)
 
-	// we process policies that are not set of background processing as we need to perform policy violation
-	// cleanup when a policy is deleted.
+	// we process policies that are not set of background processing
+	// as we need to clean up GRs when a policy is deleted
 	pc.enqueuePolicy(p)
+	pc.enqueueRCRDeletedPolicy(p.Name)
 }
 
 func (pc *PolicyController) addNsPolicy(obj interface{}) {
@@ -257,7 +259,7 @@ func (pc *PolicyController) updateNsPolicy(old, cur interface{}) {
 
 	logger.V(4).Info("updating namespace policy", "namespace", oldP.Namespace, "name", oldP.Name)
 
-	pc.enqueueDeletedRule(ConvertPolicyToClusterPolicy(oldP), ncurP)
+	pc.enqueueRCRDeletedRule(ConvertPolicyToClusterPolicy(oldP), ncurP)
 	pc.enqueuePolicy(ncurP)
 }
 
@@ -267,7 +269,7 @@ func (pc *PolicyController) deleteNsPolicy(obj interface{}) {
 	if !ok {
 		tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
 		if !ok {
-			logger.Info("couldnt get object from tomstone", "obj", obj)
+			logger.Info("couldn't get object from tombstone", "obj", obj)
 			return
 		}
 
@@ -280,12 +282,13 @@ func (pc *PolicyController) deleteNsPolicy(obj interface{}) {
 	pol := ConvertPolicyToClusterPolicy(p)
 	logger.V(4).Info("deleting namespace policy", "namespace", pol.Namespace, "name", pol.Name)
 
-	// we process policies that are not set of background processing as we need to perform policy violation
-	// cleanup when a policy is deleted.
+	// we process policies that are not set of background processing
+	// as we need to clean up GRs when a policy is deleted
 	pc.enqueuePolicy(pol)
+	pc.enqueueRCRDeletedPolicy(p.Name)
 }
 
-func (pc *PolicyController) enqueueDeletedRule(old, cur *kyverno.ClusterPolicy) {
+func (pc *PolicyController) enqueueRCRDeletedRule(old, cur *kyverno.ClusterPolicy) {
 	curRule := make(map[string]bool)
 	for _, rule := range cur.Spec.Rules {
 		curRule[rule.Name] = true
@@ -295,14 +298,24 @@ func (pc *PolicyController) enqueueDeletedRule(old, cur *kyverno.ClusterPolicy)
 		if !curRule[rule.Name] {
 			pc.prGenerator.Add(policyreport.Info{
 				PolicyName: cur.GetName(),
-				Rules: []kyverno.ViolatedRule{
-					{Name: rule.Name},
+				Results: []policyreport.EngineResponseResult{
+					{
+						Rules: []kyverno.ViolatedRule{
+							{Name: rule.Name},
+						},
+					},
 				},
 			})
 		}
 	}
 }
 
+func (pc *PolicyController) enqueueRCRDeletedPolicy(policyName string) {
+	pc.prGenerator.Add(policyreport.Info{
+		PolicyName: policyName,
+	})
+}
+
 func (pc *PolicyController) enqueuePolicy(policy *kyverno.ClusterPolicy) {
 	logger := pc.log
 	key, err := cache.MetaNamespaceKeyFunc(policy)
@@ -372,7 +385,7 @@ func (pc *PolicyController) handleErr(err error, key interface{}) {
 }
 
 func (pc *PolicyController) syncPolicy(key string) error {
-	logger := pc.log
+	logger := pc.log.WithName("syncPolicy")
 	startTime := time.Now()
 	logger.V(4).Info("started syncing policy", "key", key, "startTime", startTime)
 	defer func() {
@@ -384,55 +397,57 @@ func (pc *PolicyController) syncPolicy(key string) error {
 		logger.Error(err, "failed to list generate request")
 	}
 
-	var policy *kyverno.ClusterPolicy
-	namespace, key, isNamespacedPolicy := parseNamespacedPolicy(key)
-	if !isNamespacedPolicy {
-		policy, err = pc.pLister.Get(key)
-	} else {
-		var nspolicy *kyverno.Policy
-		nspolicy, err = pc.npLister.Policies(namespace).Get(key)
-		if err == nil && nspolicy != nil {
-			policy = ConvertPolicyToClusterPolicy(nspolicy)
-		}
-	}
-
+	policy, err := pc.getPolicy(key)
 	if err != nil {
 		if errors.IsNotFound(err) {
-			for _, v := range grList {
-				if key == v.Spec.Policy {
-					err := pc.kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(context.TODO(), v.GetName(), metav1.DeleteOptions{})
-					if err != nil && !errors.IsNotFound(err) {
-						logger.Error(err, "failed to delete gr")
-					}
-				}
-			}
-
-			go pc.removeResultsEntryFromPolicyReport(key)
+			deleteGR(pc.kyvernoClient, key, grList, logger)
 			return nil
 		}
 
 		return err
 	}
 
+	updateGR(pc.kyvernoClient, policy.Name, grList, logger)
+	pc.processExistingResources(policy)
+	return nil
+}
+
+func (pc *PolicyController) getPolicy(key string) (policy *kyverno.ClusterPolicy, err error) {
+	namespace, key, isNamespacedPolicy := parseNamespacedPolicy(key)
+	if !isNamespacedPolicy {
+		return pc.pLister.Get(key)
+	}
+
+	nsPolicy, err := pc.npLister.Policies(namespace).Get(key)
+	if err == nil && nsPolicy != nil {
+		policy = ConvertPolicyToClusterPolicy(nsPolicy)
+	}
+
+	return
+}
+
+func deleteGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*kyverno.GenerateRequest, logger logr.Logger) {
 	for _, v := range grList {
-		if policy.Name == v.Spec.Policy {
+		if policyKey == v.Spec.Policy {
+			err := kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Delete(context.TODO(), v.GetName(), metav1.DeleteOptions{})
+			if err != nil && !errors.IsNotFound(err) {
+				logger.Error(err, "failed to delete gr", "name", v.GetName())
+			}
+		}
+	}
+}
+
+func updateGR(kyvernoClient *kyvernoclient.Clientset, policyKey string, grList []*kyverno.GenerateRequest, logger logr.Logger) {
+	for _, v := range grList {
+		if policyKey == v.Spec.Policy {
 			v.SetLabels(map[string]string{
 				"policy-update": fmt.Sprintf("revision-count-%d", rand.Intn(100000)),
 			})
-			_, err := pc.kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{})
+			_, err := kyvernoClient.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Update(context.TODO(), v, metav1.UpdateOptions{})
 			if err != nil {
-				logger.Error(err, "failed to update gr", "policy", policy.GetName(), "gr", v.GetName())
+				logger.Error(err, "failed to update gr", "name", v.GetName())
 			}
 		}
 	}
 
-	engineResponses := pc.processExistingResources(policy)
-	pc.cleanupAndReport(engineResponses)
-	return nil
-}
-
-func (pc *PolicyController) removeResultsEntryFromPolicyReport(policyName string) {
-	pc.prGenerator.Add(policyreport.Info{
-		PolicyName: policyName,
-	})
 }
diff --git a/pkg/policyreport/builder.go b/pkg/policyreport/builder.go
index 82fc74700d..062e57ac4f 100755
--- a/pkg/policyreport/builder.go
+++ b/pkg/policyreport/builder.go
@@ -15,15 +15,13 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
 )
 
 const (
 	clusterreportchangerequest string = "clusterreportchangerequest"
-	resourceLabelName          string = "kyverno.io/resource.name"
-	resourceLabelKind          string = "kyverno.io/resource.kind"
 	resourceLabelNamespace     string = "kyverno.io/resource.namespace"
-	policyLabel                string = "kyverno.io/policy"
-	deletedLabelResource       string = "kyverno.io/delete.resource"
+	deletedLabelResource       string = "kyverno.io/delete.resource.name"
 	deletedLabelResourceKind   string = "kyverno.io/delete.resource.kind"
 	deletedLabelPolicy         string = "kyverno.io/delete.policy"
 	deletedLabelRule           string = "kyverno.io/delete.rule"
@@ -76,33 +74,18 @@ func NewBuilder(cpolLister kyvernolister.ClusterPolicyLister, polLister kyvernol
 
 func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured, err error) {
 	results := []*report.PolicyReportResult{}
-	for _, rule := range info.Rules {
-		if rule.Type != utils.Validation.String() {
-			continue
-		}
+	for _, infoResult := range info.Results {
+		for _, rule := range infoResult.Rules {
+			if rule.Type != utils.Validation.String() {
+				continue
+			}
 
-		result := &report.PolicyReportResult{
-			Policy: info.PolicyName,
-			Resources: []*v1.ObjectReference{
-				{
-					Kind:       info.Resource.GetKind(),
-					Namespace:  info.Resource.GetNamespace(),
-					APIVersion: info.Resource.GetAPIVersion(),
-					Name:       info.Resource.GetName(),
-					UID:        info.Resource.GetUID(),
-				},
-			},
-			Scored:   true,
-			Category: builder.fetchCategory(info.PolicyName, info.Resource.GetNamespace()),
+			result := builder.buildRCRResult(info.PolicyName, infoResult.Resource, rule)
+			results = append(results, result)
 		}
-
-		result.Rule = rule.Name
-		result.Message = rule.Message
-		result.Status = report.PolicyStatus(rule.Check)
-		results = append(results, result)
 	}
 
-	if info.Resource.GetNamespace() != "" {
+	if info.Namespace != "" {
 		rr := &request.ReportChangeRequest{
 			Summary: calculateSummary(results),
 			Results: results,
@@ -129,58 +112,84 @@ func (builder *requestBuilder) build(info Info) (req *unstructured.Unstructured,
 		set(req, info)
 	}
 
-	// deletion of a result entry
-	if len(info.Rules) == 0 && info.PolicyName == "" { // on resource deleteion
-		req.SetLabels(map[string]string{
-			resourceLabelNamespace:   info.Resource.GetNamespace(),
-			deletedLabelResource:     info.Resource.GetName(),
-			deletedLabelResourceKind: info.Resource.GetKind()})
-	} else if info.PolicyName != "" && reflect.DeepEqual(info.Resource, unstructured.Unstructured{}) { // on policy deleteion
-		req.SetKind("ReportChangeRequest")
-
-		if len(info.Rules) == 0 {
-			req.SetLabels(map[string]string{
-				deletedLabelPolicy: info.PolicyName})
-
-			req.SetName(fmt.Sprintf("reportchangerequest-%s", info.PolicyName))
-		} else {
-			req.SetLabels(map[string]string{
-				deletedLabelPolicy: info.PolicyName,
-				deletedLabelRule:   info.Rules[0].Name})
-			req.SetName(fmt.Sprintf("reportchangerequest-%s-%s", info.PolicyName, info.Rules[0].Name))
+	if !setRequestLabels(req, info) {
+		if len(results) == 0 {
+			// return nil on empty result without a deletion
+			return nil, nil
 		}
-	} else if len(results) == 0 {
-		// return nil on empty result without a deletion
-		return nil, nil
 	}
 
 	return req, nil
 }
 
+func (builder *requestBuilder) buildRCRResult(policy string, resource response.ResourceSpec, rule kyverno.ViolatedRule) *report.PolicyReportResult {
+	result := &report.PolicyReportResult{
+		Policy: policy,
+		Resources: []*v1.ObjectReference{
+			{
+				Kind:       resource.Kind,
+				Namespace:  resource.Namespace,
+				APIVersion: resource.APIVersion,
+				Name:       resource.Name,
+				UID:        types.UID(resource.UID),
+			},
+		},
+		Scored:   true,
+		Category: builder.fetchCategory(policy, resource.Namespace),
+	}
+
+	result.Rule = rule.Name
+	result.Message = rule.Message
+	result.Status = report.PolicyStatus(rule.Check)
+	return result
+}
+
 func set(obj *unstructured.Unstructured, info Info) {
-	resource := info.Resource
-	obj.SetNamespace(config.KyvernoNamespace)
 	obj.SetAPIVersion(request.SchemeGroupVersion.Group + "/" + request.SchemeGroupVersion.Version)
-	if resource.GetNamespace() == "" {
+
+	if info.Namespace == "" {
 		obj.SetGenerateName(clusterreportchangerequest + "-")
 		obj.SetKind("ClusterReportChangeRequest")
 	} else {
-		obj.SetGenerateName("reportchangerequest-")
+		obj.SetGenerateName("rcr-")
 		obj.SetKind("ReportChangeRequest")
+		obj.SetNamespace(config.KyvernoNamespace)
 	}
 
 	obj.SetLabels(map[string]string{
-		resourceLabelNamespace: resource.GetNamespace(),
-		resourceLabelName:      resource.GetName(),
-		resourceLabelKind:      resource.GetKind(),
-		policyLabel:            info.PolicyName,
+		resourceLabelNamespace: info.Namespace,
 	})
+}
 
-	if info.FromSync {
-		obj.SetAnnotations(map[string]string{
-			"fromSync": "true",
+func setRequestLabels(req *unstructured.Unstructured, info Info) bool {
+	switch {
+	case isResourceDeletion(info):
+		req.SetLabels(map[string]string{
+			resourceLabelNamespace:   info.Results[0].Resource.Namespace,
+			deletedLabelResource:     info.Results[0].Resource.Name,
+			deletedLabelResourceKind: info.Results[0].Resource.Kind,
 		})
+		return true
+
+	case isPolicyDeletion(info):
+		req.SetKind("ReportChangeRequest")
+		req.SetGenerateName("rcr-")
+		req.SetLabels(map[string]string{
+			deletedLabelPolicy: info.PolicyName},
+		)
+		return true
+
+	case isRuleDeletion(info):
+		req.SetKind("ReportChangeRequest")
+		req.SetGenerateName("rcr-")
+		req.SetLabels(map[string]string{
+			deletedLabelPolicy: info.PolicyName,
+			deletedLabelRule:   info.Results[0].Rules[0].Name},
+		)
+		return true
 	}
+
+	return false
 }
 
 func calculateSummary(results []*report.PolicyReportResult) (summary report.PolicyReportSummary) {
@@ -204,8 +213,13 @@ func calculateSummary(results []*report.PolicyReportResult) (summary report.Poli
 func buildPVInfo(er response.EngineResponse) Info {
 	info := Info{
 		PolicyName: er.PolicyResponse.Policy,
-		Resource:   er.PatchedResource,
-		Rules:      buildViolatedRules(er),
+		Namespace:  er.PatchedResource.GetNamespace(),
+		Results: []EngineResponseResult{
+			{
+				Resource: er.GetResourceSpec(),
+				Rules:    buildViolatedRules(er),
+			},
+		},
 	}
 	return info
 }
@@ -246,3 +260,21 @@ func (builder *requestBuilder) fetchCategory(policy, ns string) string {
 
 	return ""
 }
+
+func isResourceDeletion(info Info) bool {
+	return info.PolicyName == "" && len(info.Results) == 1 && info.GetRuleLength() == 0
+}
+
+func isPolicyDeletion(info Info) bool {
+	return info.PolicyName != "" && len(info.Results) == 0
+}
+
+func isRuleDeletion(info Info) bool {
+	if info.PolicyName != "" && len(info.Results) == 1 {
+		result := info.Results[0]
+		if len(result.Rules) == 1 && reflect.DeepEqual(result.Resource, response.ResourceSpec{}) {
+			return true
+		}
+	}
+	return false
+}
diff --git a/pkg/policyreport/policyreport.go b/pkg/policyreport/policyreport.go
index 42afc981fe..7df457b3ba 100644
--- a/pkg/policyreport/policyreport.go
+++ b/pkg/policyreport/policyreport.go
@@ -15,27 +15,39 @@ type deletedResource struct {
 	kind, ns, name string
 }
 
+func buildLabelForDeletedResource(labels map[string]string) *deletedResource {
+	ok := true
+	kind, kindOk := labels[deletedLabelResourceKind]
+	ok = ok && kindOk
+
+	name, nameOk := labels[deletedLabelResource]
+	ok = ok && nameOk
+
+	if !ok {
+		return nil
+	}
+
+	return &deletedResource{
+		kind: kind,
+		name: name,
+		ns:   labels[resourceLabelNamespace],
+	}
+}
+
 func getDeletedResources(aggregatedRequests interface{}) (resources []deletedResource) {
 	if requests, ok := aggregatedRequests.([]*changerequest.ClusterReportChangeRequest); ok {
 		for _, request := range requests {
-			labels := request.GetLabels()
-			dr := deletedResource{
-				kind: labels[deletedLabelResourceKind],
-				name: labels[deletedLabelResource],
-				ns:   labels[resourceLabelNamespace],
+			dr := buildLabelForDeletedResource(request.GetLabels())
+			if dr != nil {
+				resources = append(resources, *dr)
 			}
-
-			resources = append(resources, dr)
 		}
 	} else if requests, ok := aggregatedRequests.([]*changerequest.ReportChangeRequest); ok {
 		for _, request := range requests {
-			labels := request.GetLabels()
-			dr := deletedResource{
-				kind: labels[deletedLabelResourceKind],
-				name: labels[deletedLabelResource],
-				ns:   labels[resourceLabelNamespace],
+			dr := buildLabelForDeletedResource(request.GetLabels())
+			if dr != nil {
+				resources = append(resources, *dr)
 			}
-			resources = append(resources, dr)
 		}
 	}
 	return
@@ -115,9 +127,16 @@ func generateHashKey(result map[string]interface{}, dr deletedResource) (string,
 
 	resource := resources[0].(map[string]interface{})
 	if !reflect.DeepEqual(dr, deletedResource{}) {
-		if resource["kind"] == dr.kind && resource["name"] == dr.name && resource["namespace"] == dr.ns {
-			return "", false
+		if resource["kind"] == dr.kind {
+			if resource["name"] == dr.name && resource["namespace"] == dr.ns {
+				return "", false
+			}
+
+			if dr.kind == "Namespace" && resource["name"] == dr.name {
+				return "", false
+			}
 		}
+
 	}
 
 	return fmt.Sprintf(
diff --git a/pkg/policyreport/reportcontroller.go b/pkg/policyreport/reportcontroller.go
index 25a458be38..d6e923f346 100644
--- a/pkg/policyreport/reportcontroller.go
+++ b/pkg/policyreport/reportcontroller.go
@@ -232,6 +232,8 @@ func (g *ReportGenerator) handleErr(err error, key interface{}) {
 // syncHandler reconciles clusterPolicyReport if namespace == ""
 // otherwise it updates policyReport
 func (g *ReportGenerator) syncHandler(key string) error {
+	g.log.V(4).Info("syncing policy report", "key", key)
+
 	if policy, rule, ok := isDeletedPolicyKey(key); ok {
 		return g.removePolicyEntryFromReport(policy, rule)
 	}
@@ -312,6 +314,31 @@ func (g *ReportGenerator) createReportIfNotPresent(namespace string, new *unstru
 }
 
 func (g *ReportGenerator) removePolicyEntryFromReport(policyName, ruleName string) error {
+	if err := g.removeFromClusterPolicyReport(policyName, ruleName); err != nil {
+		return err
+	}
+
+	if err := g.removeFromPolicyReport(policyName, ruleName); err != nil {
+		return err
+	}
+
+	labelset := labels.Set(map[string]string{deletedLabelPolicy: policyName})
+	if ruleName != "" {
+		labelset = labels.Set(map[string]string{
+			deletedLabelPolicy: policyName,
+			deletedLabelRule:   ruleName,
+		})
+	}
+	aggregatedRequests, err := g.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace).List(labels.SelectorFromSet(labelset))
+	if err != nil {
+		return err
+	}
+
+	g.cleanupReportRequests(aggregatedRequests)
+	return nil
+}
+
+func (g *ReportGenerator) removeFromClusterPolicyReport(policyName, ruleName string) error {
 	cpolrs, err := g.clusterReportLister.List(labels.Everything())
 	if err != nil {
 		return fmt.Errorf("failed to list clusterPolicyReport %v", err)
@@ -335,7 +362,10 @@ func (g *ReportGenerator) removePolicyEntryFromReport(policyName, ruleName strin
 			return fmt.Errorf("failed to update clusterPolicyReport %s %v", cpolr.Name, err)
 		}
 	}
+	return nil
+}
 
+func (g *ReportGenerator) removeFromPolicyReport(policyName, ruleName string) error {
 	namespaces, err := g.dclient.ListResource("", "Namespace", "", nil)
 	if err != nil {
 		return fmt.Errorf("unable to list namespace %v", err)
@@ -370,23 +400,10 @@ func (g *ReportGenerator) removePolicyEntryFromReport(policyName, ruleName strin
 			return fmt.Errorf("failed to update PolicyReport %s %v", r.GetName(), err)
 		}
 	}
-
-	labelset := labels.Set(map[string]string{deletedLabelPolicy: policyName})
-	if ruleName != "" {
-		labelset = labels.Set(map[string]string{
-			deletedLabelPolicy: policyName,
-			deletedLabelRule:   ruleName,
-		})
-	}
-	aggregatedRequests, err := g.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace).List(labels.SelectorFromSet(labelset))
-	if err != nil {
-		return err
-	}
-
-	g.cleanupReportRequests(aggregatedRequests)
 	return nil
 }
 
+// aggregateReports aggregates cluster / report change requests to a policy report
 func (g *ReportGenerator) aggregateReports(namespace string) (
 	report *unstructured.Unstructured, aggregatedRequests interface{}, err error) {
 
@@ -553,7 +570,7 @@ func (g *ReportGenerator) updateReport(old interface{}, new *unstructured.Unstru
 	new.Object = obj
 
 	if !hasResultsChanged(oldUnstructured, new.UnstructuredContent()) {
-		g.log.V(4).Info("unchanged policy report", "namespace", new.GetNamespace(), "name", new.GetName())
+		g.log.V(4).Info("unchanged policy report", "kind", new.GetKind(), "namespace", new.GetNamespace(), "name", new.GetName())
 		return nil
 	}
 
diff --git a/pkg/policyreport/reportrequest.go b/pkg/policyreport/reportrequest.go
index acc185e91c..9021d0d683 100755
--- a/pkg/policyreport/reportrequest.go
+++ b/pkg/policyreport/reportrequest.go
@@ -19,6 +19,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/constant"
 	client "github.com/kyverno/kyverno/pkg/dclient"
 	dclient "github.com/kyverno/kyverno/pkg/dclient"
+	"github.com/kyverno/kyverno/pkg/engine/response"
 	"github.com/kyverno/kyverno/pkg/policystatus"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -61,11 +62,7 @@ type Generator struct {
 
 	queue     workqueue.RateLimitingInterface
 	dataStore *dataStore
-
-	// update policy status with violationCount
-	policyStatusListener policystatus.Listener
-
-	log logr.Logger
+	log       logr.Logger
 }
 
 // NewReportChangeRequestGenerator returns a new instance of report request generator
@@ -89,7 +86,6 @@ func NewReportChangeRequestGenerator(client *policyreportclient.Clientset,
 		polListerSynced:                  polInformer.Informer().HasSynced,
 		queue:                            workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName),
 		dataStore:                        newDataStore(),
-		policyStatusListener:             policyStatus,
 		log:                              log,
 	}
 
@@ -128,32 +124,47 @@ func (ds *dataStore) delete(keyHash string) {
 	delete(ds.data, keyHash)
 }
 
-//Info is a request to create PV
+// Info stores the policy application results for all matched resources
+// Namespace is set to empty "" if resource is cluster wide resource
 type Info struct {
 	PolicyName string
-	Resource   unstructured.Unstructured
-	Rules      []kyverno.ViolatedRule
-	FromSync   bool
+	Namespace  string
+	Results    []EngineResponseResult
 }
 
-func (i Info) toKey() string {
+type EngineResponseResult struct {
+	Resource response.ResourceSpec
+	Rules    []kyverno.ViolatedRule
+}
+
+func (i Info) ToKey() string {
 	keys := []string{
 		i.PolicyName,
-		i.Resource.GetKind(),
-		i.Resource.GetNamespace(),
-		i.Resource.GetName(),
-		strconv.Itoa(len(i.Rules)),
+		i.Namespace,
+		strconv.Itoa(len(i.Results)),
+	}
+
+	for _, result := range i.Results {
+		keys = append(keys, result.Resource.GetKey())
 	}
 	return strings.Join(keys, "/")
 }
 
+func (i Info) GetRuleLength() int {
+	l := 0
+	for _, res := range i.Results {
+		l += len(res.Rules)
+	}
+	return l
+}
+
 // GeneratorInterface provides API to create PVs
 type GeneratorInterface interface {
 	Add(infos ...Info)
 }
 
 func (gen *Generator) enqueue(info Info) {
-	keyHash := info.toKey()
+	keyHash := info.ToKey()
 	gen.dataStore.add(keyHash, info)
 	gen.queue.Add(keyHash)
 }
@@ -231,7 +242,7 @@ func (gen *Generator) processNextWorkItem() bool {
 		info := gen.dataStore.lookup(keyHash)
 		if reflect.DeepEqual(info, Info{}) {
 			gen.queue.Forget(obj)
-			logger.V(3).Info("empty key")
+			logger.V(4).Info("empty key")
 			return nil
 		}
 
@@ -249,48 +260,48 @@ func (gen *Generator) processNextWorkItem() bool {
 
 func (gen *Generator) syncHandler(info Info) error {
 	gen.log.V(3).Info("generating report change request")
+
 	builder := NewBuilder(gen.cpolLister, gen.polLister)
-	reportChangeRequestUnstructured, err := builder.build(info)
+	rcrUnstructured, err := builder.build(info)
 	if err != nil {
 		return fmt.Errorf("unable to build reportChangeRequest: %v", err)
 	}
 
-	if reportChangeRequestUnstructured == nil {
+	if rcrUnstructured == nil {
 		return nil
 	}
 
-	return gen.sync(reportChangeRequestUnstructured, info)
+	return gen.sync(rcrUnstructured, info)
 }
 
 func (gen *Generator) sync(reportReq *unstructured.Unstructured, info Info) error {
-	defer func() {
-		if val := reportReq.GetAnnotations()["fromSync"]; val == "true" {
-			gen.policyStatusListener.Update(violationCount{
-				policyName:    info.PolicyName,
-				violatedRules: info.Rules,
-			})
-		}
-	}()
-
-	logger := gen.log.WithName("sync")
+	logger := gen.log.WithName("sync report change request")
 	reportReq.SetCreationTimestamp(v1.Now())
-	if reportReq.GetNamespace() == "" {
-		old, err := gen.clusterReportChangeRequestLister.Get(reportReq.GetName())
-		if err != nil {
-			if apierrors.IsNotFound(err) {
-				if _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), "", reportReq, false); err != nil {
-					return fmt.Errorf("failed to create clusterReportChangeRequest: %v", err)
-				}
-
-				logger.V(3).Info("successfully created clusterReportChangeRequest", "name", reportReq.GetName())
-				return nil
-			}
-			return fmt.Errorf("unable to get %s: %v", reportReq.GetKind(), err)
-		}
-
-		return updateReportChangeRequest(gen.dclient, old, reportReq, logger)
+	if reportReq.GetKind() == "ClusterReportChangeRequest" {
+		return gen.syncClusterReportChangeRequest(reportReq, logger)
 	}
 
+	return gen.syncReportChangeRequest(reportReq, logger)
+}
+
+func (gen *Generator) syncClusterReportChangeRequest(reportReq *unstructured.Unstructured, logger logr.Logger) error {
+	old, err := gen.clusterReportChangeRequestLister.Get(reportReq.GetName())
+	if err != nil {
+		if apierrors.IsNotFound(err) {
+			if _, err = gen.dclient.CreateResource(reportReq.GetAPIVersion(), reportReq.GetKind(), "", reportReq, false); err != nil {
+				return fmt.Errorf("failed to create clusterReportChangeRequest: %v", err)
+			}
+
+			logger.V(3).Info("successfully created clusterReportChangeRequest", "name", reportReq.GetName())
+			return nil
+		}
+		return fmt.Errorf("unable to get %s: %v", reportReq.GetKind(), err)
+	}
+
+	return updateReportChangeRequest(gen.dclient, old, reportReq, logger)
+}
+
+func (gen *Generator) syncReportChangeRequest(reportReq *unstructured.Unstructured, logger logr.Logger) error {
 	old, err := gen.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace).Get(reportReq.GetName())
 	if err != nil {
 		if apierrors.IsNotFound(err) {
diff --git a/pkg/webhooks/validation.go b/pkg/webhooks/validation.go
index 727791e9d0..e91780d686 100644
--- a/pkg/webhooks/validation.go
+++ b/pkg/webhooks/validation.go
@@ -130,22 +130,27 @@ func HandleValidation(
 	prGenerator.Add(prInfos...)
 
 	if request.Operation == v1beta1.Delete {
-		prGenerator.Add(policyreport.Info{
-			Resource: unstructured.Unstructured{
-				Object: map[string]interface{}{
-					"kind": oldR.GetKind(),
-					"metadata": map[string]interface{}{
-						"name":      oldR.GetName(),
-						"namespace": oldR.GetNamespace(),
-					},
-				},
-			},
-		})
+		prGenerator.Add(buildDeletionPrInfo(oldR))
 	}
 
 	return true, ""
 }
 
+func buildDeletionPrInfo(oldR unstructured.Unstructured) policyreport.Info {
+	return policyreport.Info{
+		Namespace: oldR.GetNamespace(),
+		Results: []policyreport.EngineResponseResult{
+			{Resource: response.ResourceSpec{
+				Kind:       oldR.GetKind(),
+				APIVersion: oldR.GetAPIVersion(),
+				Namespace:  oldR.GetNamespace(),
+				Name:       oldR.GetName(),
+				UID:        string(oldR.GetUID()),
+			}},
+		},
+	}
+}
+
 type validateStats struct {
 	resp      response.EngineResponse
 	namespace string