From cd9e596e7ed263a168b7cb84948c47f1c59e747d Mon Sep 17 00:00:00 2001
From: Pooja Singh <singhpooja240393@gmail.com>
Date: Thu, 1 Jul 2021 00:30:02 +0530
Subject: [PATCH] [Improvement] Kyverno should not delete downstream resources
 when a generate policy using the clone behavior has synchronize: true (#1880)

* debuging issue

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* issue fixed

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* remove policy name in source resource

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* fixed deletion of GR on source updation

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added function in common

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* removing comments

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* added generated resource list to the log

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>

* small improvement

Signed-off-by: NoSkillGirl <singhpooja240393@gmail.com>
---
 go.sum                             |  1 +
 pkg/common/common.go               | 46 ++++++++++++++++++++++++++++++
 pkg/generate/cleanup/controller.go | 33 ++++++++++++++++++---
 pkg/policy/validate_controller.go  | 12 ++++++--
 pkg/webhooks/generation.go         | 29 +++++++++++++------
 5 files changed, 106 insertions(+), 15 deletions(-)

diff --git a/go.sum b/go.sum
index e0a75ddfa7..b8b881eda4 100644
--- a/go.sum
+++ b/go.sum
@@ -1019,6 +1019,7 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
 github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
 github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
 github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
 github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
diff --git a/pkg/common/common.go b/pkg/common/common.go
index 7ed33318b0..039e5c8119 100644
--- a/pkg/common/common.go
+++ b/pkg/common/common.go
@@ -7,6 +7,8 @@ import (
 	"time"
 
 	"github.com/go-logr/logr"
+	kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
+	dclient "github.com/kyverno/kyverno/pkg/dclient"
 	enginutils "github.com/kyverno/kyverno/pkg/engine/utils"
 	"github.com/pkg/errors"
 	v1 "k8s.io/api/core/v1"
@@ -136,3 +138,47 @@ func RetryFunc(retryInterval, timeout time.Duration, run func() error, logger lo
 		return nil
 	}
 }
+
+func ProcessDeletePolicyForCloneGenerateRule(rules []kyverno.Rule, client *dclient.Client, pName string, logger logr.Logger) bool {
+	generatePolicyWithClone := false
+	for _, rule := range rules {
+		if rule.Generation.Clone.Name != "" {
+			logger.V(4).Info("generate policy with clone, skipping deletion of generate request")
+			generatePolicyWithClone = true
+			obj, err := client.GetResource("", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name)
+			if err != nil {
+				logger.Error(err, fmt.Sprintf("source resource %s/%s/%s not found.", rule.Generation.Kind, rule.Generation.Clone.Namespace, rule.Generation.Clone.Name))
+				continue
+			}
+
+			updateSource := true
+			label := obj.GetLabels()
+			logger.V(4).Info("removing policy name from label of source resource")
+
+			if len(label) != 0 {
+				if label["generate.kyverno.io/clone-policy-name"] != "" {
+					policyNames := label["generate.kyverno.io/clone-policy-name"]
+					if strings.Contains(policyNames, pName) {
+						updatedPolicyNames := strings.Replace(policyNames, pName, "", -1)
+						label["generate.kyverno.io/clone-policy-name"] = updatedPolicyNames
+					} else {
+						updateSource = false
+					}
+				}
+			}
+
+			if updateSource {
+				logger.V(4).Info("updating existing clone source")
+				obj.SetLabels(label)
+				_, err = client.UpdateResource(obj.GetAPIVersion(), rule.Generation.Kind, rule.Generation.Clone.Namespace, obj, false)
+				if err != nil {
+					logger.Error(err, "failed to update source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
+					continue
+				}
+				logger.V(4).Info("updated source", "kind", obj.GetKind(), "name", obj.GetName(), "namespace", obj.GetNamespace())
+			}
+		}
+	}
+
+	return generatePolicyWithClone
+}
diff --git a/pkg/generate/cleanup/controller.go b/pkg/generate/cleanup/controller.go
index e1b2e33e72..ac7265d7bb 100644
--- a/pkg/generate/cleanup/controller.go
+++ b/pkg/generate/cleanup/controller.go
@@ -10,10 +10,12 @@ import (
 	kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
 	kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
 	kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
+	pkgCommon "github.com/kyverno/kyverno/pkg/common"
 	"github.com/kyverno/kyverno/pkg/config"
 	dclient "github.com/kyverno/kyverno/pkg/dclient"
 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
+	"k8s.io/apimachinery/pkg/labels"
 	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
 	"k8s.io/apimachinery/pkg/util/wait"
 	"k8s.io/client-go/dynamic/dynamicinformer"
@@ -139,14 +141,37 @@ func (c *Controller) deletePolicy(obj interface{}) {
 	// clean up the GR
 	// Get the corresponding GR
 	// get the list of GR for the current Policy version
-	grs, err := c.grLister.GetGenerateRequestsForClusterPolicy(p.Name)
+	rules := p.Spec.Rules
+
+	generatePolicyWithClone := pkgCommon.ProcessDeletePolicyForCloneGenerateRule(rules, c.client, p.GetName(), logger)
+
+	// get the generated resource name from generate request for log
+	selector := labels.SelectorFromSet(labels.Set(map[string]string{
+		"generate.kyverno.io/policy-name": p.Name,
+	}))
+
+	grList, err := c.grLister.List(selector)
 	if err != nil {
-		logger.Error(err, "failed to generate request CR for the policy", "name", p.Name)
+		logger.Error(err, "failed to get generate request for the resource", "label", "generate.kyverno.io/policy-name")
 		return
 	}
 
-	for _, gr := range grs {
-		c.addGR(gr)
+	for _, gr := range grList {
+		for _, generatedResource := range gr.Status.GeneratedResources {
+			logger.Info("retaining resource", "APIVersion", generatedResource.APIVersion, "Kind", generatedResource.Kind, "Name", generatedResource.Name, "Nmaespace", generatedResource.Namespace)
+		}
+	}
+
+	if !generatePolicyWithClone {
+		grs, err := c.grLister.GetGenerateRequestsForClusterPolicy(p.Name)
+		if err != nil {
+			logger.Error(err, "failed to generate request CR for the policy", "name", p.Name)
+			return
+		}
+
+		for _, gr := range grs {
+			c.addGR(gr)
+		}
 	}
 }
 
diff --git a/pkg/policy/validate_controller.go b/pkg/policy/validate_controller.go
index e8dbb0dd73..2b89d57617 100644
--- a/pkg/policy/validate_controller.go
+++ b/pkg/policy/validate_controller.go
@@ -15,6 +15,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/client/clientset/versioned/scheme"
 	kyvernoinformer "github.com/kyverno/kyverno/pkg/client/informers/externalversions/kyverno/v1"
 	kyvernolister "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
+	pkgCommon "github.com/kyverno/kyverno/pkg/common"
 	"github.com/kyverno/kyverno/pkg/config"
 	client "github.com/kyverno/kyverno/pkg/dclient"
 	"github.com/kyverno/kyverno/pkg/event"
@@ -341,8 +342,15 @@ func (pc *PolicyController) deletePolicy(obj interface{}) {
 
 	// 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)
+	// skip generate policies with clone
+	rules := p.Spec.Rules
+
+	generatePolicyWithClone := pkgCommon.ProcessDeletePolicyForCloneGenerateRule(rules, pc.client, p.GetName(), logger)
+
+	if !generatePolicyWithClone {
+		pc.enqueuePolicy(p)
+		pc.enqueueRCRDeletedPolicy(p.Name)
+	}
 }
 
 func (pc *PolicyController) registerPolicyRuleInfoMetricAddNsPolicy(logger logr.Logger, p *kyverno.Policy) {
diff --git a/pkg/webhooks/generation.go b/pkg/webhooks/generation.go
index 2756b7740e..07497a0012 100644
--- a/pkg/webhooks/generation.go
+++ b/pkg/webhooks/generation.go
@@ -154,18 +154,29 @@ func (ws *WebhookServer) handleUpdate(request *v1beta1.AdmissionRequest, policie
 func (ws *WebhookServer) handleUpdateCloneSourceResource(resLabels map[string]string, logger logr.Logger) {
 	policyNames := strings.Split(resLabels["generate.kyverno.io/clone-policy-name"], ",")
 	for _, policyName := range policyNames {
-		selector := labels.SelectorFromSet(labels.Set(map[string]string{
-			"generate.kyverno.io/policy-name": policyName,
-		}))
 
-		grList, err := ws.grLister.List(selector)
+		// check if the policy exists
+		_, err := ws.kyvernoClient.KyvernoV1().ClusterPolicies().Get(contextdefault.TODO(), policyName, metav1.GetOptions{})
 		if err != nil {
-			logger.Error(err, "failed to get generate request for the resource", "label", "generate.kyverno.io/policy-name")
-			return
-		}
-		for _, gr := range grList {
-			ws.updateAnnotationInGR(gr, logger)
+			if strings.Contains(err.Error(), "not found") {
+				logger.V(4).Info("skipping updation of generate request as policy is deleted")
+			} else {
+				logger.Error(err, "failed to get generate policy", "Name", policyName)
+				selector := labels.SelectorFromSet(labels.Set(map[string]string{
+					"generate.kyverno.io/policy-name": policyName,
+				}))
+
+				grList, err := ws.grLister.List(selector)
+				if err != nil {
+					logger.Error(err, "failed to get generate request for the resource", "label", "generate.kyverno.io/policy-name")
+					return
+				}
+				for _, gr := range grList {
+					ws.updateAnnotationInGR(gr, logger)
+				}
+			}
 		}
+
 	}
 }