diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 57c972e91f..91db7c6402 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -70,6 +70,7 @@ var ( clientRateLimitQPS float64 clientRateLimitBurst int changeRequestLimit int + splitPolicyReport bool webhookRegistrationTimeout time.Duration setupLog = log.Log.WithName("setup") ) @@ -94,7 +95,7 @@ func main() { flag.Func(toggle.AutogenInternalsFlagName, toggle.AutogenInternalsDescription, toggle.AutogenInternalsFlag) flag.DurationVar(&webhookRegistrationTimeout, "webhookRegistrationTimeout", 120*time.Second, "Timeout for webhook registration, e.g., 30s, 1m, 5m.") flag.IntVar(&changeRequestLimit, "maxReportChangeRequests", 1000, "maximum pending report change requests per namespace or for the cluster-wide policy report") - + flag.BoolVar(&splitPolicyReport, "splitPolicyReport", false, "Set the flag to 'true', to enable the split-up PolicyReports per policy.") if err := flag.Set("v", "2"); err != nil { setupLog.Error(err, "failed to set log level") os.Exit(1) @@ -206,6 +207,7 @@ func main() { kyvernoV1.ClusterPolicies(), kyvernoV1.Policies(), changeRequestLimit, + splitPolicyReport, log.Log.WithName("ReportChangeRequestGenerator"), ) @@ -218,6 +220,7 @@ func main() { kyvernoV1alpha2.ClusterReportChangeRequests(), kubeInformer.Core().V1().Namespaces(), reportReqGen.CleanupChangeRequest, + splitPolicyReport, log.Log.WithName("PolicyReportGenerator"), ) if err != nil { diff --git a/pkg/policyreport/builder.go b/pkg/policyreport/builder.go index d346b2ac74..b1da866e6d 100644 --- a/pkg/policyreport/builder.go +++ b/pkg/policyreport/builder.go @@ -28,6 +28,7 @@ const ( // the following labels are used to list rcr / crcr ResourceLabelNamespace string = "kyverno.io/resource.namespace" + policyLabel string = "kyverno.io/policy-name" deletedLabelPolicy string = "kyverno.io/delete.policy" deletedLabelRule string = "kyverno.io/delete.rule" @@ -56,6 +57,13 @@ func GeneratePolicyReportName(ns string) string { return name } +func TrimmedName(s string) string { + if len(s) > 63 { + return s[:63] + } + return s +} + // GeneratePRsFromEngineResponse generate Violations from engine responses func GeneratePRsFromEngineResponse(ers []*response.EngineResponse, log logr.Logger) (pvInfos []Info) { for _, er := range ers { @@ -209,6 +217,7 @@ func set(obj *unstructured.Unstructured, info Info) { obj.SetLabels(map[string]string{ ResourceLabelNamespace: info.Namespace, + policyLabel: TrimmedName(info.PolicyName), appVersion: version.BuildVersion, }) } diff --git a/pkg/policyreport/changerequestcreator.go b/pkg/policyreport/changerequestcreator.go index 27929dd433..9caad97ad8 100644 --- a/pkg/policyreport/changerequestcreator.go +++ b/pkg/policyreport/changerequestcreator.go @@ -36,19 +36,23 @@ type changeRequestCreator struct { mutex sync.RWMutex queue []string + // splitPolicyReport enable/disable the PolicyReport split-up per policy feature + splitPolicyReport bool + tickerInterval time.Duration log logr.Logger } -func newChangeRequestCreator(client kyvernoclient.Interface, tickerInterval time.Duration, log logr.Logger) creator { +func newChangeRequestCreator(client kyvernoclient.Interface, tickerInterval time.Duration, splitPolicyReport bool, log logr.Logger) creator { return &changeRequestCreator{ - client: client, - RCRCache: cache.New(0, 24*time.Hour), - CRCRCache: cache.New(0, 24*time.Hour), - queue: []string{}, - tickerInterval: tickerInterval, - log: log, + client: client, + RCRCache: cache.New(0, 24*time.Hour), + CRCRCache: cache.New(0, 24*time.Hour), + queue: []string{}, + tickerInterval: tickerInterval, + splitPolicyReport: splitPolicyReport, + log: log, } } @@ -110,10 +114,23 @@ func (c *changeRequestCreator) run(stopChan <-chan struct{}) { ticker := time.NewTicker(c.tickerInterval) defer ticker.Stop() + if c.splitPolicyReport { + err := CleanupPolicyReport(c.client) + if err != nil { + c.log.Error(err, "failed to delete old reports") + } + } + for { select { case <-ticker.C: - requests, size := c.mergeRequests() + requests := []*unstructured.Unstructured{} + var size int + if c.splitPolicyReport { + requests, size = c.mergeRequestsPerPolicy() + } else { + requests, size = c.mergeRequests() + } for _, request := range requests { if err := c.create(request); err != nil { c.log.Error(err, "failed to create report change request", "req", request.Object) @@ -216,7 +233,96 @@ func (c *changeRequestCreator) mergeRequests() (results []*unstructured.Unstruct results = append(results, mergedNamespacedRCR) } } + return +} +// mergeRequests merges all current cached requests per policy +// it blocks writing to the cache +func (c *changeRequestCreator) mergeRequestsPerPolicy() (results []*unstructured.Unstructured, size int) { + c.mutex.Lock() + defer c.mutex.Unlock() + + mergedCRCR := make(map[string]*unstructured.Unstructured) + mergedRCR := make(map[string]*unstructured.Unstructured) + size = len(c.queue) + + for _, uid := range c.queue { + if unstr, ok := c.CRCRCache.Get(uid); ok { + if crcr, ok := unstr.(*unstructured.Unstructured); ok { + policyName := crcr.GetLabels()[policyLabel] + mergedPolicyCRCR, ok := mergedCRCR[policyName] + if !ok { + mergedPolicyCRCR = &unstructured.Unstructured{} + } + + if isDeleteRequest(crcr) { + if !reflect.DeepEqual(mergedPolicyCRCR, &unstructured.Unstructured{}) { + results = append(results, mergedPolicyCRCR) + mergedCRCR[policyName] = &unstructured.Unstructured{} + } + + results = append(results, crcr) + } else { + if reflect.DeepEqual(mergedPolicyCRCR, &unstructured.Unstructured{}) { + mergedCRCR[policyName] = crcr + continue + } + + if ok := merge(mergedPolicyCRCR, crcr); !ok { + results = append(results, mergedPolicyCRCR) + mergedCRCR[policyName] = crcr + } else { + mergedCRCR[policyName] = mergedPolicyCRCR + } + } + } + continue + } + + if unstr, ok := c.RCRCache.Get(uid); ok { + if rcr, ok := unstr.(*unstructured.Unstructured); ok { + policyName := rcr.GetLabels()[policyLabel] + resourceNS := rcr.GetLabels()[ResourceLabelNamespace] + mergedNamespacedRCR, ok := mergedRCR[policyName+resourceNS] + if !ok { + mergedNamespacedRCR = &unstructured.Unstructured{} + } + + if isDeleteRequest(rcr) { + if !reflect.DeepEqual(mergedNamespacedRCR, &unstructured.Unstructured{}) { + results = append(results, mergedNamespacedRCR) + mergedRCR[policyName+resourceNS] = &unstructured.Unstructured{} + } + + results = append(results, rcr) + } else { + if reflect.DeepEqual(mergedNamespacedRCR, &unstructured.Unstructured{}) { + mergedRCR[policyName+resourceNS] = rcr + continue + } + + if ok := merge(mergedNamespacedRCR, rcr); !ok { + results = append(results, mergedNamespacedRCR) + mergedRCR[policyName+resourceNS] = rcr + } else { + mergedRCR[policyName+resourceNS] = mergedNamespacedRCR + } + } + } + } + } + + for _, mergedPolicyCRCR := range mergedCRCR { + if !reflect.DeepEqual(mergedPolicyCRCR, &unstructured.Unstructured{}) { + results = append(results, mergedPolicyCRCR) + } + } + + for _, mergedNamespacedRCR := range mergedRCR { + if !reflect.DeepEqual(mergedNamespacedRCR, &unstructured.Unstructured{}) { + results = append(results, mergedNamespacedRCR) + } + } return } diff --git a/pkg/policyreport/policyreport.go b/pkg/policyreport/policyreport.go index 9e058eea4e..0f98b3a736 100644 --- a/pkg/policyreport/policyreport.go +++ b/pkg/policyreport/policyreport.go @@ -1,6 +1,7 @@ package policyreport import ( + "context" "encoding/json" "fmt" "reflect" @@ -12,7 +13,10 @@ import ( kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned" kyvernov1alpha2listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1alpha2" policyreportv1alpha2listers "github.com/kyverno/kyverno/pkg/client/listers/policyreport/v1alpha2" + "github.com/kyverno/kyverno/pkg/config" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/labels" ) type PolicyReportEraser interface { @@ -220,3 +224,41 @@ func mapToStruct(in, out interface{}) error { jsonBytes, _ := json.Marshal(in) return json.Unmarshal(jsonBytes, out) } + +func CleanupPolicyReport(client kyvernoclient.Interface) error { + var errors []string + var gracePeriod int64 = 0 + + deleteOptions := metav1.DeleteOptions{GracePeriodSeconds: &gracePeriod} + selector := labels.SelectorFromSet(labels.Set(map[string]string{LabelSelectorKey: LabelSelectorValue})) + + err := client.KyvernoV1alpha2().ClusterReportChangeRequests().DeleteCollection(context.TODO(), deleteOptions, metav1.ListOptions{}) + if err != nil { + errors = append(errors, err.Error()) + } + + err = client.KyvernoV1alpha2().ReportChangeRequests(config.KyvernoNamespace()).DeleteCollection(context.TODO(), deleteOptions, metav1.ListOptions{}) + if err != nil { + errors = append(errors, err.Error()) + } + + err = client.Wgpolicyk8sV1alpha2().ClusterPolicyReports().DeleteCollection(context.TODO(), deleteOptions, metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + errors = append(errors, err.Error()) + } + + reports, err := client.Wgpolicyk8sV1alpha2().PolicyReports(metav1.NamespaceAll).List(context.TODO(), metav1.ListOptions{LabelSelector: selector.String()}) + if err != nil { + errors = append(errors, err.Error()) + } + for _, report := range reports.Items { + err = client.Wgpolicyk8sV1alpha2().PolicyReports(report.Namespace).Delete(context.TODO(), report.Name, metav1.DeleteOptions{}) + if err != nil { + errors = append(errors, err.Error()) + } + } + if len(errors) == 0 { + return nil + } + return fmt.Errorf("%v", strings.Join(errors, ";")) +} diff --git a/pkg/policyreport/reportcontroller.go b/pkg/policyreport/reportcontroller.go index 73037a090a..6dbfde12cb 100644 --- a/pkg/policyreport/reportcontroller.go +++ b/pkg/policyreport/reportcontroller.go @@ -67,6 +67,8 @@ type ReportGenerator struct { reportChangeRequestLister kyvernov1alpha2listers.ReportChangeRequestLister clusterReportChangeRequestLister kyvernov1alpha2listers.ClusterReportChangeRequestLister nsLister corev1listers.NamespaceLister + // splitPolicyReport enable/disable the PolicyReport split-up per policy feature + splitPolicyReport bool informersSynced []cache.InformerSynced @@ -91,6 +93,7 @@ func NewReportGenerator( clusterReportReqInformer kyvernov1alpha2informers.ClusterReportChangeRequestInformer, namespace corev1informers.NamespaceInformer, cleanupChangeRequest chan<- ReconcileInfo, + splitPolicyReport bool, log logr.Logger, ) (*ReportGenerator, error) { gen := &ReportGenerator{ @@ -101,6 +104,7 @@ func NewReportGenerator( reportReqInformer: reportReqInformer, clusterReportReqInformer: clusterReportReqInformer, queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), prWorkQueueName), + splitPolicyReport: splitPolicyReport, ReconcileCh: make(chan bool, 10), cleanupChangeRequest: cleanupChangeRequest, log: log, @@ -120,7 +124,7 @@ func NewReportGenerator( // - for the resource // - "" for cluster wide resource // - "deletedpolicy/policyName/ruleName(optional)" for a deleted policy or rule -func generateCacheKey(changeRequest interface{}) string { +func (g *ReportGenerator) generateCacheKey(changeRequest interface{}) string { if request, ok := changeRequest.(*kyvernov1alpha2.ReportChangeRequest); ok { label := request.GetLabels() policy := label[deletedLabelPolicy] @@ -133,7 +137,12 @@ func generateCacheKey(changeRequest interface{}) string { if ns == "" { ns = "default" } - return ns + if g.splitPolicyReport { + policy = label[policyLabel] + return strings.Join([]string{ns, policy}, "/") + } else { + return ns + } } else if request, ok := changeRequest.(*kyvernov1alpha2.ClusterReportChangeRequest); ok { label := request.GetLabels() policy := label[deletedLabelPolicy] @@ -141,9 +150,13 @@ func generateCacheKey(changeRequest interface{}) string { if rule != "" || policy != "" { return strings.Join([]string{deletedPolicyKey, policy, rule}, "/") } - return "" + if g.splitPolicyReport { + policy = label[policyLabel] + return strings.Join([]string{"", policy}, "/") + } else { + return "" + } } - return "" } @@ -171,7 +184,7 @@ func (g *ReportGenerator) addReportChangeRequest(obj interface{}) { return } - key := generateCacheKey(obj) + key := g.generateCacheKey(obj) g.queue.Add(key) } @@ -187,7 +200,7 @@ func (g *ReportGenerator) updateReportChangeRequest(old interface{}, cur interfa return } - key := generateCacheKey(cur) + key := g.generateCacheKey(cur) g.queue.Add(key) } @@ -197,7 +210,7 @@ func (g *ReportGenerator) addClusterReportChangeRequest(obj interface{}) { return } - key := generateCacheKey(obj) + key := g.generateCacheKey(obj) g.queue.Add(key) } @@ -213,7 +226,8 @@ func (g *ReportGenerator) updateClusterReportChangeRequest(old interface{}, cur return } - g.queue.Add("") + key := g.generateCacheKey(cur) + g.queue.Add(key) } func (g *ReportGenerator) deletePolicyReport(obj interface{}) { @@ -321,22 +335,43 @@ func (g *ReportGenerator) handleErr(err error, key interface{}, aggregatedReques } // syncHandler reconciles clusterPolicyReport if namespace == "" -// otherwise it updates policyReport +// otherwise it updates policyReport. the key is of type "namespace/policyname" func (g *ReportGenerator) syncHandler(key string) (aggregatedRequests interface{}, err error) { g.log.V(4).Info("syncing policy report", "key", key) - namespace := key if policy, rule, ok := isDeletedPolicyKey(key); ok { g.log.V(4).Info("sync policy report on policy deletion") return g.removePolicyEntryFromReport(policy, rule) } - - new, aggregatedRequests, err := g.aggregateReports(namespace) + var namespace, policyName string + if g.splitPolicyReport { + namespace = strings.Split(key, "/")[0] + policyName = strings.Split(key, "/")[1] + } else { + namespace = key + } + new, aggregatedRequests, err := g.aggregateReports(namespace, policyName) if err != nil { return aggregatedRequests, fmt.Errorf("failed to aggregate reportChangeRequest results %v", err) } - report, err := g.reportLister.PolicyReports(namespace).Get(GeneratePolicyReportName(namespace)) + if g.splitPolicyReport { + deleteResources := getDeletedResources(aggregatedRequests) + if len(deleteResources) != 0 { + for _, dr := range deleteResources { + if err := g.updateReportsForDeletedResource(dr.name, new, aggregatedRequests); err != nil { + return aggregatedRequests, err + } + } + } + } + + var report *policyreportv1alpha2.PolicyReport + if g.splitPolicyReport { + report, err = g.reportLister.PolicyReports(namespace).Get(TrimmedName(GeneratePolicyReportName(namespace) + "-" + policyName)) + } else { + report, err = g.reportLister.PolicyReports(namespace).Get(GeneratePolicyReportName(namespace)) + } if err == nil { if val, ok := report.GetLabels()[inactiveLabelKey]; ok && val == inactiveLabelVal { g.log.Info("got resourceExhausted error, please opt-in via \"splitPolicyReport\" to generate report per policy") @@ -344,8 +379,9 @@ func (g *ReportGenerator) syncHandler(key string) (aggregatedRequests interface{ } } + // Delete changes request does not have the policyName label set var old interface{} - if old, err = g.createReportIfNotPresent(namespace, new, aggregatedRequests); err != nil { + if old, err = g.createReportIfNotPresent(namespace, policyName, new, aggregatedRequests); err != nil { return aggregatedRequests, err } @@ -365,7 +401,7 @@ func (g *ReportGenerator) syncHandler(key string) (aggregatedRequests interface{ // createReportIfNotPresent creates cluster / policyReport if not present // return the existing report if exist -func (g *ReportGenerator) createReportIfNotPresent(namespace string, new *unstructured.Unstructured, aggregatedRequests interface{}) (report interface{}, err error) { +func (g *ReportGenerator) createReportIfNotPresent(namespace, policyName string, new *unstructured.Unstructured, aggregatedRequests interface{}) (report interface{}, err error) { log := g.log.WithName("createReportIfNotPresent") obj, hasDuplicate, err := updateResults(new.UnstructuredContent(), new.UnstructuredContent(), nil) if hasDuplicate && err != nil { @@ -387,7 +423,11 @@ func (g *ReportGenerator) createReportIfNotPresent(namespace string, new *unstru return nil, nil } - report, err = g.reportLister.PolicyReports(namespace).Get(GeneratePolicyReportName(namespace)) + if g.splitPolicyReport { + report, err = g.reportLister.PolicyReports(namespace).Get(TrimmedName(GeneratePolicyReportName(namespace) + "-" + policyName)) + } else { + report, err = g.reportLister.PolicyReports(namespace).Get(GeneratePolicyReportName(namespace)) + } if err != nil { if apierrors.IsNotFound(err) && new != nil { polr, err := convertToPolr(new) @@ -407,7 +447,12 @@ func (g *ReportGenerator) createReportIfNotPresent(namespace string, new *unstru return nil, fmt.Errorf("unable to get policyReport: %v", err) } } else { - report, err = g.clusterReportLister.Get(GeneratePolicyReportName(namespace)) + + if g.splitPolicyReport { + report, err = g.clusterReportLister.Get(TrimmedName(GeneratePolicyReportName(namespace) + "-" + policyName)) + } else { + report, err = g.clusterReportLister.Get(GeneratePolicyReportName(namespace)) + } if err != nil { if apierrors.IsNotFound(err) { if new != nil { @@ -485,6 +530,7 @@ func (g *ReportGenerator) removeFromClusterPolicyReport(policyName, ruleName str } func (g *ReportGenerator) removeFromPolicyReport(policyName, ruleName string) error { + namespaces, err := g.client.ListResource("", "Namespace", "", nil) if err != nil { return fmt.Errorf("unable to list namespace %v", err) @@ -520,6 +566,7 @@ func (g *ReportGenerator) removeFromPolicyReport(policyName, ruleName string) er gv := policyreportv1alpha2.SchemeGroupVersion gvk := schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: "PolicyReport"} r.SetGroupVersionKind(gvk) + if _, err := g.pclient.Wgpolicyk8sV1alpha2().PolicyReports(r.GetNamespace()).Update(context.TODO(), r, metav1.UpdateOptions{}); err != nil { return fmt.Errorf("failed to update PolicyReport %s %v", r.GetName(), err) } @@ -528,7 +575,7 @@ func (g *ReportGenerator) removeFromPolicyReport(policyName, ruleName string) er } // aggregateReports aggregates cluster / report change requests to a policy report -func (g *ReportGenerator) aggregateReports(namespace string) ( +func (g *ReportGenerator) aggregateReports(namespace, policyName string) ( report *unstructured.Unstructured, aggregatedRequests interface{}, err error, @@ -538,14 +585,19 @@ func (g *ReportGenerator) aggregateReports(namespace string) ( g.log.Error(err, "failed to get Kyverno namespace, policy reports will not be garbage collected upon termination") } + selector := labels.NewSelector() if namespace == "" { - selector := labels.SelectorFromSet(labels.Set(map[string]string{appVersion: version.BuildVersion})) + if g.splitPolicyReport { + selector = labels.SelectorFromSet(labels.Set(map[string]string{appVersion: version.BuildVersion, policyLabel: TrimmedName(policyName)})) + } else { + selector = labels.SelectorFromSet(labels.Set(map[string]string{appVersion: version.BuildVersion})) + } requests, err := g.clusterReportChangeRequestLister.List(selector) if err != nil { return nil, nil, fmt.Errorf("unable to list ClusterReportChangeRequests within: %v", err) } - if report, aggregatedRequests, err = mergeRequests(nil, kyvernoNamespace, requests); err != nil { + if report, aggregatedRequests, err = g.mergeRequests(nil, kyvernoNamespace, policyName, requests); err != nil { return nil, nil, fmt.Errorf("unable to merge ClusterReportChangeRequests results: %v", err) } } else { @@ -561,13 +613,17 @@ func (g *ReportGenerator) aggregateReports(namespace string) ( ns.SetDeletionTimestamp(&now) } - selector := labels.SelectorFromSet(labels.Set(map[string]string{appVersion: version.BuildVersion, ResourceLabelNamespace: namespace})) + if g.splitPolicyReport { + selector = labels.SelectorFromSet(labels.Set(map[string]string{appVersion: version.BuildVersion, ResourceLabelNamespace: namespace, policyLabel: TrimmedName(policyName)})) + } else { + selector = labels.SelectorFromSet(labels.Set(map[string]string{appVersion: version.BuildVersion, ResourceLabelNamespace: namespace})) + } requests, err := g.reportChangeRequestLister.ReportChangeRequests(config.KyvernoNamespace()).List(selector) if err != nil { return nil, nil, fmt.Errorf("unable to list reportChangeRequests within namespace %s: %v", ns, err) } - if report, aggregatedRequests, err = mergeRequests(ns, kyvernoNamespace, requests); err != nil { + if report, aggregatedRequests, err = g.mergeRequests(ns, kyvernoNamespace, policyName, requests); err != nil { return nil, nil, fmt.Errorf("unable to merge results: %v", err) } } @@ -575,9 +631,8 @@ func (g *ReportGenerator) aggregateReports(namespace string) ( return report, aggregatedRequests, nil } -func mergeRequests(ns, kyvernoNs *corev1.Namespace, requestsGeneral interface{}) (*unstructured.Unstructured, interface{}, error) { +func (g *ReportGenerator) mergeRequests(ns, kyvernoNs *corev1.Namespace, policyName string, requestsGeneral interface{}) (*unstructured.Unstructured, interface{}, error) { results := []policyreportv1alpha2.PolicyReportResult{} - if requests, ok := requestsGeneral.([]*kyvernov1alpha2.ClusterReportChangeRequest); ok { aggregatedRequests := []*kyvernov1alpha2.ClusterReportChangeRequest{} for _, request := range requests { @@ -601,7 +656,9 @@ func mergeRequests(ns, kyvernoNs *corev1.Namespace, requestsGeneral interface{}) } req := &unstructured.Unstructured{Object: obj} - setReport(req, nil, kyvernoNs) + + g.setReport(req, ns, kyvernoNs, policyName) + return req, aggregatedRequests, nil } @@ -628,7 +685,8 @@ func mergeRequests(ns, kyvernoNs *corev1.Namespace, requestsGeneral interface{}) } req := &unstructured.Unstructured{Object: obj} - setReport(req, ns, kyvernoNs) + + g.setReport(req, ns, kyvernoNs, policyName) return req, aggregatedRequests, nil } @@ -636,7 +694,7 @@ func mergeRequests(ns, kyvernoNs *corev1.Namespace, requestsGeneral interface{}) return nil, nil, nil } -func setReport(reportUnstructured *unstructured.Unstructured, ns, kyvernoNs *corev1.Namespace) { +func (g *ReportGenerator) setReport(reportUnstructured *unstructured.Unstructured, ns, kyvernoNs *corev1.Namespace, policyname string) { reportUnstructured.SetAPIVersion(policyreportv1alpha2.SchemeGroupVersion.String()) reportUnstructured.SetLabels(LabelSelector.MatchLabels) @@ -655,12 +713,20 @@ func setReport(reportUnstructured *unstructured.Unstructured, ns, kyvernoNs *cor } if ns == nil { - reportUnstructured.SetName(GeneratePolicyReportName("")) + if g.splitPolicyReport { + reportUnstructured.SetName(TrimmedName(GeneratePolicyReportName("") + "-" + policyname)) + } else { + reportUnstructured.SetName(GeneratePolicyReportName("")) + } reportUnstructured.SetKind("ClusterPolicyReport") return } - reportUnstructured.SetName(GeneratePolicyReportName(ns.GetName())) + if g.splitPolicyReport { + reportUnstructured.SetName(TrimmedName(GeneratePolicyReportName(ns.GetName()) + "-" + policyname)) + } else { + reportUnstructured.SetName(GeneratePolicyReportName(ns.GetName())) + } reportUnstructured.SetNamespace(ns.GetName()) reportUnstructured.SetKind("PolicyReport") } @@ -779,6 +845,62 @@ func (g *ReportGenerator) updateReport(old interface{}, new *unstructured.Unstru return } +func (g *ReportGenerator) updateReportsForDeletedResource(resName string, new *unstructured.Unstructured, aggregatedRequests interface{}) (err error) { + if _, ok := aggregatedRequests.([]*kyvernov1alpha2.ClusterReportChangeRequest); ok { + cpolrs, err := g.clusterReportLister.List(labels.Everything()) + if err != nil { + return fmt.Errorf("failed to list clusterPolicyReport %v", err) + } + for _, cpolr := range cpolrs { + newRes := []policyreportv1alpha2.PolicyReportResult{} + for _, result := range cpolr.Results { + if len(result.Resources) != 0 { + for _, res := range result.Resources { + if res.Name != resName { + newRes = append(newRes, result) + } + } + } + } + cpolr.Results = newRes + cpolr.Summary = calculateSummary(newRes) + gv := policyreportv1alpha2.SchemeGroupVersion + cpolr.SetGroupVersionKind(schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: "ClusterPolicyReport"}) + if _, err := g.pclient.Wgpolicyk8sV1alpha2().ClusterPolicyReports().Update(context.TODO(), cpolr, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("failed to update clusterPolicyReport %s %v", cpolr.Name, err) + } + } + } else { + polrs, err := g.reportLister.List(labels.Everything()) + if err != nil { + return fmt.Errorf("failed to list clusterPolicyReport %v", err) + } + for _, polr := range polrs { + newRes1 := []policyreportv1alpha2.PolicyReportResult{} + for _, result := range polr.Results { + if len(result.Resources) != 0 { + for _, res := range result.Resources { + if res.Name != resName { + newRes1 = append(newRes1, result) + } + } + } + } + polr.Results = newRes1 + polr.Summary = calculateSummary(newRes1) + gv := policyreportv1alpha2.SchemeGroupVersion + + polr.SetGroupVersionKind(schema.GroupVersionKind{Group: gv.Group, Version: gv.Version, Kind: "PolicyReport"}) + + if _, err := g.pclient.Wgpolicyk8sV1alpha2().PolicyReports(new.GetNamespace()).Update(context.TODO(), polr, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("failed to update clusterPolicyReport %s %v", polr.Name, err) + } + } + } + + return +} + func (g *ReportGenerator) cleanupReportRequests(requestsGeneral interface{}) { defer g.log.V(5).Info("successfully cleaned up report requests") if requests, ok := requestsGeneral.([]*kyvernov1alpha2.ReportChangeRequest); ok { diff --git a/pkg/policyreport/reportrequest.go b/pkg/policyreport/reportrequest.go index 898d0e76f3..f9dbd79a3a 100644 --- a/pkg/policyreport/reportrequest.go +++ b/pkg/policyreport/reportrequest.go @@ -70,6 +70,7 @@ func NewReportChangeRequestGenerator(client kyvernoclient.Interface, cpolInformer kyvernov1informers.ClusterPolicyInformer, polInformer kyvernov1informers.PolicyInformer, changeRequestLimit int, + splitPolicyReport bool, log logr.Logger, ) *Generator { gen := Generator{ @@ -81,9 +82,9 @@ func NewReportChangeRequestGenerator(client kyvernoclient.Interface, polLister: polInformer.Lister(), queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), workQueueName), dataStore: newDataStore(), - requestCreator: newChangeRequestCreator(client, 3*time.Second, log.WithName("requestCreator")), changeRequestLimit: changeRequestLimit, CleanupChangeRequest: make(chan ReconcileInfo, 10), + requestCreator: newChangeRequestCreator(client, 3*time.Second, splitPolicyReport, log.WithName("requestCreator")), log: log, }