From 4438b24b695e02f0002324945525b0cc3d841065 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Charles-Edouard=20Br=C3=A9t=C3=A9ch=C3=A9?=
 <charles.edouard@nirmata.com>
Date: Thu, 28 Mar 2024 09:03:01 +0100
Subject: [PATCH] refactor: exception selector interface (#9907)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* refactor: exception selector interface

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

* fix

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>

---------

Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
Co-authored-by: shuting <shuting@nirmata.com>
---
 .../processor/policy_processor.go             |  3 +-
 cmd/internal/engine.go                        |  8 +++--
 pkg/engine/api/selector.go                    | 15 +++-----
 pkg/engine/exceptions.go                      |  9 ++---
 pkg/exceptions/selector.go                    | 34 +++++++++++++++++++
 pkg/webhooks/resource/fake.go                 |  3 +-
 6 files changed, 51 insertions(+), 21 deletions(-)
 create mode 100644 pkg/exceptions/selector.go

diff --git a/cmd/cli/kubectl-kyverno/processor/policy_processor.go b/cmd/cli/kubectl-kyverno/processor/policy_processor.go
index 32e245b328..34663a75cd 100644
--- a/cmd/cli/kubectl-kyverno/processor/policy_processor.go
+++ b/cmd/cli/kubectl-kyverno/processor/policy_processor.go
@@ -26,6 +26,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/engine/jmespath"
 	"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
 	"github.com/kyverno/kyverno/pkg/engine/policycontext"
+	"github.com/kyverno/kyverno/pkg/exceptions"
 	"github.com/kyverno/kyverno/pkg/imageverifycache"
 	"github.com/kyverno/kyverno/pkg/registryclient"
 	jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
@@ -80,7 +81,7 @@ func (p *PolicyProcessor) ApplyPoliciesOnResource() ([]engineapi.EngineResponse,
 		factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil),
 		imageverifycache.DisabledImageVerifyCache(),
 		store.ContextLoaderFactory(p.Store, nil),
-		policyExceptionLister,
+		exceptions.New(policyExceptionLister),
 	)
 	gvk, subresource := resource.GroupVersionKind(), ""
 	// If --cluster flag is not set, then we need to find the top level resource GVK and subresource
diff --git a/cmd/internal/engine.go b/cmd/internal/engine.go
index 1fd5e99564..13ff1e9062 100644
--- a/cmd/internal/engine.go
+++ b/cmd/internal/engine.go
@@ -18,6 +18,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/engine/context/resolvers"
 	"github.com/kyverno/kyverno/pkg/engine/factories"
 	"github.com/kyverno/kyverno/pkg/engine/jmespath"
+	"github.com/kyverno/kyverno/pkg/exceptions"
 	"github.com/kyverno/kyverno/pkg/imageverifycache"
 	"github.com/kyverno/kyverno/pkg/registryclient"
 	"k8s.io/client-go/kubernetes"
@@ -66,16 +67,17 @@ func NewExceptionSelector(
 	var exceptionsLister engineapi.PolicyExceptionSelector
 	if enablePolicyException {
 		factory := kyvernoinformer.NewSharedInformerFactory(kyvernoClient, resyncPeriod)
-		lister := factory.Kyverno().V2beta1().PolicyExceptions().Lister()
+		var lister exceptions.Lister
 		if exceptionNamespace != "" {
-			exceptionsLister = lister.PolicyExceptions(exceptionNamespace)
+			lister = factory.Kyverno().V2beta1().PolicyExceptions().Lister().PolicyExceptions(exceptionNamespace)
 		} else {
-			exceptionsLister = lister
+			lister = factory.Kyverno().V2beta1().PolicyExceptions().Lister()
 		}
 		// start informers and wait for cache sync
 		if !StartInformersAndWaitForCacheSync(ctx, logger, factory) {
 			checkError(logger, errors.New("failed to wait for cache sync"), "failed to wait for cache sync")
 		}
+		exceptionsLister = exceptions.New(lister)
 	}
 	return exceptionsLister
 }
diff --git a/pkg/engine/api/selector.go b/pkg/engine/api/selector.go
index 44435680a9..2727b06d75 100644
--- a/pkg/engine/api/selector.go
+++ b/pkg/engine/api/selector.go
@@ -2,16 +2,11 @@ package api
 
 import (
 	kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
-	"k8s.io/apimachinery/pkg/labels"
 )
 
-// NamespacedResourceSelector is an abstract interface used to list namespaced resources given a label selector
-// Any implementation might exist, cache based, file based, client based etc...
-type NamespacedResourceSelector[T any] interface {
-	// List selects resources based on label selector.
-	// Objects returned here must be treated as read-only.
-	List(selector labels.Selector) (ret []T, err error)
-}
-
 // PolicyExceptionSelector is an abstract interface used to resolve poliicy exceptions
-type PolicyExceptionSelector = NamespacedResourceSelector[*kyvernov2beta1.PolicyException]
+type PolicyExceptionSelector interface {
+	// Find returns policy exceptions matching a given policy name and rule name.
+	// Objects returned here must be treated as read-only.
+	Find(string, string) ([]*kyvernov2beta1.PolicyException, error)
+}
diff --git a/pkg/engine/exceptions.go b/pkg/engine/exceptions.go
index 8683753c70..c925891e4d 100644
--- a/pkg/engine/exceptions.go
+++ b/pkg/engine/exceptions.go
@@ -3,7 +3,6 @@ package engine
 import (
 	kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
 	kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
-	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/client-go/tools/cache"
 )
 
@@ -16,15 +15,13 @@ func (e *engine) GetPolicyExceptions(
 	if e.exceptionSelector == nil {
 		return exceptions, nil
 	}
-	polexs, err := e.exceptionSelector.List(labels.Everything())
+	policyName := cache.MetaObjectToName(policy).String()
+	polexs, err := e.exceptionSelector.Find(policyName, rule)
 	if err != nil {
 		return exceptions, err
 	}
-	policyName := cache.MetaObjectToName(policy).String()
 	for _, polex := range polexs {
-		if polex.Contains(policyName, rule) {
-			exceptions = append(exceptions, *polex)
-		}
+		exceptions = append(exceptions, *polex)
 	}
 	return exceptions, nil
 }
diff --git a/pkg/exceptions/selector.go b/pkg/exceptions/selector.go
new file mode 100644
index 0000000000..486e69691b
--- /dev/null
+++ b/pkg/exceptions/selector.go
@@ -0,0 +1,34 @@
+package exceptions
+
+import (
+	kyvernov2beta1 "github.com/kyverno/kyverno/api/kyverno/v2beta1"
+	"k8s.io/apimachinery/pkg/labels"
+)
+
+type Lister interface {
+	List(labels.Selector) ([]*kyvernov2beta1.PolicyException, error)
+}
+
+type selector struct {
+	lister Lister
+}
+
+func New(lister Lister) selector {
+	return selector{
+		lister: lister,
+	}
+}
+
+func (s selector) Find(policyName string, ruleName string) ([]*kyvernov2beta1.PolicyException, error) {
+	polexs, err := s.lister.List(labels.Everything())
+	if err != nil {
+		return nil, err
+	}
+	var results []*kyvernov2beta1.PolicyException
+	for _, polex := range polexs {
+		if polex.Contains(policyName, ruleName) {
+			results = append(results, polex)
+		}
+	}
+	return results, nil
+}
diff --git a/pkg/webhooks/resource/fake.go b/pkg/webhooks/resource/fake.go
index 09d78c440e..67569def92 100644
--- a/pkg/webhooks/resource/fake.go
+++ b/pkg/webhooks/resource/fake.go
@@ -13,6 +13,7 @@ import (
 	"github.com/kyverno/kyverno/pkg/engine/factories"
 	"github.com/kyverno/kyverno/pkg/engine/jmespath"
 	"github.com/kyverno/kyverno/pkg/event"
+	"github.com/kyverno/kyverno/pkg/exceptions"
 	"github.com/kyverno/kyverno/pkg/imageverifycache"
 	"github.com/kyverno/kyverno/pkg/metrics"
 	"github.com/kyverno/kyverno/pkg/policycache"
@@ -61,7 +62,7 @@ func NewFakeHandlers(ctx context.Context, policyCache policycache.Cache) webhook
 			factories.DefaultRegistryClientFactory(adapters.RegistryClient(rclient), nil),
 			imageverifycache.DisabledImageVerifyCache(),
 			factories.DefaultContextLoaderFactory(configMapResolver),
-			peLister,
+			exceptions.New(peLister),
 		),
 	}
 }