22 changes: 16 additions & 6 deletions config/install.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ spec:
names:
categories:
- kyverno
- all
kind: AdmissionReport
listKind: AdmissionReportList
plural: admissionreports
Expand Down Expand Up @@ -376,7 +375,6 @@ spec:
names:
categories:
- kyverno
- all
kind: BackgroundScanReport
listKind: BackgroundScanReportList
plural: backgroundscanreports
Expand Down Expand Up @@ -684,7 +682,6 @@ spec:
names:
categories:
- kyverno
- all
kind: ClusterAdmissionReport
listKind: ClusterAdmissionReportList
plural: clusteradmissionreports
Expand Down Expand Up @@ -1033,7 +1030,6 @@ spec:
names:
categories:
- kyverno
- all
kind: ClusterBackgroundScanReport
listKind: ClusterBackgroundScanReportList
plural: clusterbackgroundscanreports
Expand Down Expand Up @@ -1341,7 +1337,6 @@ spec:
names:
categories:
- kyverno
- all
kind: ClusterPolicy
listKind: ClusterPolicyList
plural: clusterpolicies
Expand Down Expand Up @@ -4078,6 +4073,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -7119,6 +7116,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -9916,6 +9915,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -12932,6 +12933,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -16560,6 +16563,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -19602,6 +19607,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -22400,6 +22407,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -25416,6 +25425,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -26115,7 +26126,6 @@ spec:
names:
categories:
- kyverno
- all
kind: UpdateRequest
listKind: UpdateRequestList
plural: updaterequests
Expand Down
22 changes: 16 additions & 6 deletions config/install_debug.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ spec:
names:
categories:
- kyverno
- all
kind: AdmissionReport
listKind: AdmissionReportList
plural: admissionreports
Expand Down Expand Up @@ -373,7 +372,6 @@ spec:
names:
categories:
- kyverno
- all
kind: BackgroundScanReport
listKind: BackgroundScanReportList
plural: backgroundscanreports
Expand Down Expand Up @@ -680,7 +678,6 @@ spec:
names:
categories:
- kyverno
- all
kind: ClusterAdmissionReport
listKind: ClusterAdmissionReportList
plural: clusteradmissionreports
Expand Down Expand Up @@ -1028,7 +1025,6 @@ spec:
names:
categories:
- kyverno
- all
kind: ClusterBackgroundScanReport
listKind: ClusterBackgroundScanReportList
plural: clusterbackgroundscanreports
Expand Down Expand Up @@ -1335,7 +1331,6 @@ spec:
names:
categories:
- kyverno
- all
kind: ClusterPolicy
listKind: ClusterPolicyList
plural: clusterpolicies
Expand Down Expand Up @@ -4072,6 +4067,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -7113,6 +7110,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -9910,6 +9909,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -12926,6 +12927,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -16551,6 +16554,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -19593,6 +19598,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -22391,6 +22398,8 @@ spec:
description: PredicateType defines the type of Predicate
contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -25407,6 +25416,8 @@ spec:
description: PredicateType defines the type
of Predicate contained within the Statement.
type: string
required:
- predicateType
type: object
type: array
attestors:
Expand Down Expand Up @@ -26104,7 +26115,6 @@ spec:
names:
categories:
- kyverno
- all
kind: UpdateRequest
listKind: UpdateRequestList
plural: updaterequests
Expand Down
1 change: 0 additions & 1 deletion docs/user/crd/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -827,7 +827,6 @@ <h3 id="kyverno.io/v1.Attestation">Attestation
</em>
</td>
<td>
<em>(Optional)</em>
<p>Conditions are used to verify attributes within a Predicate. If no Conditions are specified
the attestation check is satisfied as long there are predicates that match the predicate type.</p>
</td>
Expand Down
130 changes: 79 additions & 51 deletions pkg/engine/imageVerify.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,48 +308,68 @@ func (iv *imageVerifier) verifyImage(imageVerify kyvernov1.ImageVerification, im
}

if len(imageVerify.Attestors) > 0 {
ruleResp, _, _ := iv.verifyAttestors(imageVerify.Attestors, imageVerify, imageInfo, "")
ruleResp, cosignResp := iv.verifyAttestors(imageVerify.Attestors, imageVerify, imageInfo, "")
if ruleResp.Status != response.RuleStatusPass {
return ruleResp, ""
}

if len(imageVerify.Attestations) == 0 {
return ruleResp, cosignResp.Digest
}

if imageInfo.Digest == "" {
imageInfo.Digest = cosignResp.Digest
}

if len(imageVerify.Attestations) == 0 {
return ruleResp, cosignResp.Digest
}

if imageInfo.Digest == "" {
imageInfo.Digest = cosignResp.Digest
}
}

return iv.verifyAttestations(imageVerify, imageInfo)
}

func (iv *imageVerifier) verifyAttestors(attestors []kyvernov1.AttestorSet, imageVerify kyvernov1.ImageVerification,
imageInfo apiutils.ImageInfo, predicateType string,
) (*response.RuleResponse, *cosign.Response, []kyvernov1.AttestorSet) {
func (iv *imageVerifier) verifyAttestors(
attestors []kyvernov1.AttestorSet,
imageVerify kyvernov1.ImageVerification,
imageInfo apiutils.ImageInfo,
predicateType string,
) (*response.RuleResponse, *cosign.Response) {
var cosignResponse *cosign.Response
var newAttestors []kyvernov1.AttestorSet
image := imageInfo.String()

for i, attestorSet := range attestors {
var err error
path := fmt.Sprintf(".attestors[%d]", i)
iv.logger.V(4).Info("verifying attestors", "path", path)
cosignResponse, err = iv.verifyAttestorSet(attestorSet, imageVerify, imageInfo, path, predicateType)
cosignResponse, err = iv.verifyAttestorSet(attestorSet, imageVerify, imageInfo, path)
if err != nil {
iv.logger.Error(err, "failed to verify image")
msg := fmt.Sprintf("failed to verify image %s: %s", image, err.Error())

// handle registry network errors as a rule error (instead of a policy failure)
var netErr *net.OpError
if errors.As(err, &netErr) {
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusError, nil), nil, nil
}

return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil), nil, nil
return iv.handleRegistryErrors(image, err), nil
}
newAttestors = append(newAttestors, attestors[i])
}

if cosignResponse == nil {
return ruleError(iv.rule, response.ImageVerify, "invalid response", fmt.Errorf("nil")), nil, nil
return ruleError(iv.rule, response.ImageVerify, "invalid response", fmt.Errorf("nil")), nil
}

msg := fmt.Sprintf("verified image signatures for %s", image)
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), cosignResponse, newAttestors
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), cosignResponse
}

// handle registry network errors as a rule error (instead of a policy failure)
func (iv *imageVerifier) handleRegistryErrors(image string, err error) *response.RuleResponse {
msg := fmt.Sprintf("failed to verify image %s: %s", image, err.Error())
var netErr *net.OpError
if errors.As(err, &netErr) {
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusError, nil)
}

return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil)
}

func (iv *imageVerifier) verifyAttestations(imageVerify kyvernov1.ImageVerification, imageInfo apiutils.ImageInfo) (*response.RuleResponse, string) {
Expand All @@ -358,62 +378,66 @@ func (iv *imageVerifier) verifyAttestations(imageVerify kyvernov1.ImageVerificat
var attestationError error
path := fmt.Sprintf(".attestations[%d]", i)

attestors := attestation.Attestors
if attestation.PredicateType == "" {
return ruleResponse(*iv.rule, response.ImageVerify, path+": missing predicateType", response.RuleStatusFail, nil), ""
}

if len(attestation.Attestors) == 0 {
attestors = []kyvernov1.AttestorSet{{}}
// add an empty attestor to allow fetching and checking attestations
attestation.Attestors = []kyvernov1.AttestorSet{{Entries: []kyvernov1.Attestor{{}}}}
}

for j, attestor := range attestors {
for j, attestor := range attestation.Attestors {
attestorPath := fmt.Sprintf("%s.attestors[%d]", path, j)

requiredCount := getRequiredCount(attestor)
verifiedCount := 0

entries := attestor.Entries
if len(entries) == 0 {
entries = []kyvernov1.Attestor{{}}
}

for _, a := range entries {
for _, a := range attestor.Entries {
entryPath := fmt.Sprintf("%s.entries[%d]", attestorPath, i)
opts, subPath := iv.buildOptionsAndPath(a, imageVerify, image, attestation)
opts, subPath := iv.buildOptionsAndPath(a, imageVerify, image, &imageVerify.Attestations[i])
cosignResp, err := cosign.FetchAttestations(*opts)
if err != nil {
iv.logger.Error(err, "failed to fetch attestations")
msg := fmt.Sprintf("failed to fetch attestations %s: %s", image, err.Error())
// handle registry network errors as a rule error (instead of a policy failure)
var netErr *net.OpError
if errors.As(err, &netErr) {
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusError, nil), ""
}

return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil), ""
return iv.handleRegistryErrors(image, err), ""
}

if imageInfo.Digest == "" {
imageInfo.Digest = cosignResp.Digest
image = imageInfo.String()
}

verifiedCount++
attestationError = iv.verifyAttestation(cosignResp.Statements, attestation, imageInfo)
if attestationError != nil {
attestationError = errors.Wrapf(attestationError, entryPath+subPath)
return ruleResponse(*iv.rule, response.ImageVerify, attestationError.Error(), response.RuleStatusFail, nil), ""
}

verifiedCount++
if verifiedCount >= requiredCount {
msg := fmt.Sprintf("image attestations verification succeeded, verifiedCount: %v, requiredCount: %v", verifiedCount, requiredCount)
iv.logger.V(2).Info(msg)
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), ""
iv.logger.V(2).Info("image attestations verification succeeded", "verifiedCount", verifiedCount, "requiredCount", requiredCount)
break
}
}

if verifiedCount < requiredCount {
msg := fmt.Sprintf("image attestations verification failed, verifiedCount: %v, requiredCount: %v", verifiedCount, requiredCount)
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusFail, nil), ""
}
}

iv.logger.V(4).Info("attestation checks passed", "path", path, "image", imageInfo.String(), "predicateType", attestation.PredicateType)
}

msg := fmt.Sprintf("verified image attestations for %s", image)
iv.logger.V(2).Info(msg)
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), ""
return ruleResponse(*iv.rule, response.ImageVerify, msg, response.RuleStatusPass, nil), imageInfo.Digest
}

func (iv *imageVerifier) verifyAttestorSet(attestorSet kyvernov1.AttestorSet, imageVerify kyvernov1.ImageVerification,
imageInfo apiutils.ImageInfo, path, predicateType string,
func (iv *imageVerifier) verifyAttestorSet(
attestorSet kyvernov1.AttestorSet,
imageVerify kyvernov1.ImageVerification,
imageInfo apiutils.ImageInfo,
path string,
) (*cosign.Response, error) {
var errorList []error
verifiedCount := 0
Expand All @@ -433,10 +457,10 @@ func (iv *imageVerifier) verifyAttestorSet(attestorSet kyvernov1.AttestorSet, im
entryError = errors.Wrapf(err, "failed to unmarshal nested attestor %s", attestorPath)
} else {
attestorPath += ".attestor"
cosignResp, entryError = iv.verifyAttestorSet(*nestedAttestorSet, imageVerify, imageInfo, attestorPath, predicateType)
cosignResp, entryError = iv.verifyAttestorSet(*nestedAttestorSet, imageVerify, imageInfo, attestorPath)
}
} else {
opts, subPath := iv.buildOptionsAndPath(a, imageVerify, image, kyvernov1.Attestation{PredicateType: predicateType})
opts, subPath := iv.buildOptionsAndPath(a, imageVerify, image, nil)
cosignResp, entryError = cosign.VerifySignature(*opts)
if entryError != nil {
entryError = errors.Wrapf(entryError, attestorPath+subPath)
Expand Down Expand Up @@ -511,7 +535,7 @@ func getRequiredCount(as kyvernov1.AttestorSet) int {
return *as.Count
}

func (iv *imageVerifier) buildOptionsAndPath(attestor kyvernov1.Attestor, imageVerify kyvernov1.ImageVerification, image string, attestation kyvernov1.Attestation) (*cosign.Options, string) {
func (iv *imageVerifier) buildOptionsAndPath(attestor kyvernov1.Attestor, imageVerify kyvernov1.ImageVerification, image string, attestation *kyvernov1.Attestation) (*cosign.Options, string) {
path := ""
opts := &cosign.Options{
ImageRef: image,
Expand All @@ -523,8 +547,8 @@ func (iv *imageVerifier) buildOptionsAndPath(attestor kyvernov1.Attestor, imageV
opts.Roots = imageVerify.Roots
}

opts.PredicateType = attestation.PredicateType
if attestation.PredicateType != "" {
if attestation != nil {
opts.PredicateType = attestation.PredicateType
opts.FetchAttestations = true
}

Expand Down Expand Up @@ -573,14 +597,18 @@ func makeAddDigestPatch(imageInfo apiutils.ImageInfo, digest string) ([]byte, er
}

func (iv *imageVerifier) verifyAttestation(statements []map[string]interface{}, attestation kyvernov1.Attestation, imageInfo apiutils.ImageInfo) error {
if attestation.PredicateType == "" {
return fmt.Errorf("a predicateType is required")
}

image := imageInfo.String()
statementsByPredicate, types := buildStatementMap(statements)
iv.logger.V(4).Info("checking attestations", "predicates", types, "image", image)

statements = statementsByPredicate[attestation.PredicateType]
if statements == nil {
iv.logger.Info("attestation predicate type not found", "type", attestation.PredicateType, "predicates", types, "image", imageInfo.String())
return fmt.Errorf("predicate type %s not found", attestation.PredicateType)
iv.logger.Info("no attestations found for predicate", "type", attestation.PredicateType, "predicates", types, "image", imageInfo.String())
return fmt.Errorf("attestions not found for predicate type %s", attestation.PredicateType)
}

for _, s := range statements {
Expand Down
51 changes: 33 additions & 18 deletions pkg/engine/imageVerify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,17 @@ var testPolicyGood = `{
"attestations": [
{
"predicateType": "https://example.com/CodeReview/v1",
"attestors": [
{
"entries": [
{
"keys": {
"publicKeys": "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHMmDjK65krAyDaGaeyWNzgvIu155JI50B2vezCw8+3CVeE0lJTL5dbL3OP98Za0oAEBJcOxky8Riy/XcmfKZbw==\n-----END PUBLIC KEY-----"
}
}
]
}
],
"conditions": [
{
"all": [
Expand Down Expand Up @@ -427,28 +438,32 @@ func Test_ConfigMapMissingFailure(t *testing.T) {

func Test_SignatureGoodSigned(t *testing.T) {
policyContext := buildContext(t, testSampleSingleKeyPolicy, testSampleResource, "")
policyContext.Policy.GetSpec().Rules[0].VerifyImages[0].MutateDigest = true
cosign.ClearMock()
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusPass, err.PolicyResponse.Rules[0].Message)
engineResp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message)
assert.Equal(t, len(engineResp.PolicyResponse.Rules[0].Patches), 1)
patch := engineResp.PolicyResponse.Rules[0].Patches[0]
assert.Equal(t, string(patch), "{\"op\":\"replace\",\"path\":\"/spec/containers/0/image\",\"value\":\"ghcr.io/kyverno/test-verify-image:signed@sha256:b31bfb4d0213f254d361e0079deaaebefa4f82ba7aa76ef82e90b4935ad5b105\"}")
}

func Test_SignatureUnsigned(t *testing.T) {
cosign.ClearMock()
unsigned := strings.Replace(testSampleResource, ":signed", ":unsigned", -1)
policyContext := buildContext(t, testSampleSingleKeyPolicy, unsigned, "")
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusFail, err.PolicyResponse.Rules[0].Message)
engineResp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message)
}

func Test_SignatureWrongKey(t *testing.T) {
cosign.ClearMock()
otherKey := strings.Replace(testSampleResource, ":signed", ":signed-by-someone-else", -1)
policyContext := buildContext(t, testSampleSingleKeyPolicy, otherKey, "")
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusFail, err.PolicyResponse.Rules[0].Message)
engineResp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message)
}

func Test_SignaturesMultiKey(t *testing.T) {
Expand All @@ -457,19 +472,19 @@ func Test_SignaturesMultiKey(t *testing.T) {
policy = strings.Replace(policy, "KEY2", testVerifyImageKey, -1)
policy = strings.Replace(policy, "COUNT", "0", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusPass, err.PolicyResponse.Rules[0].Message)
engineResp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message)
}

func Test_SignaturesMultiKeyFail(t *testing.T) {
cosign.ClearMock()
policy := strings.Replace(testSampleMultipleKeyPolicy, "KEY1", testVerifyImageKey, -1)
policy = strings.Replace(policy, "COUNT", "0", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusFail, err.PolicyResponse.Rules[0].Message)
engineResp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusFail, engineResp.PolicyResponse.Rules[0].Message)
}

func Test_SignaturesMultiKeyOneGoodKey(t *testing.T) {
Expand All @@ -478,9 +493,9 @@ func Test_SignaturesMultiKeyOneGoodKey(t *testing.T) {
policy = strings.Replace(policy, "KEY2", testOtherKey, -1)
policy = strings.Replace(policy, "COUNT", "1", -1)
policyContext := buildContext(t, policy, testSampleResource, "")
err, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(err.PolicyResponse.Rules), 1)
assert.Equal(t, err.PolicyResponse.Rules[0].Status, response.RuleStatusPass, err.PolicyResponse.Rules[0].Message)
engineResp, _ := VerifyAndPatchImages(policyContext)
assert.Equal(t, len(engineResp.PolicyResponse.Rules), 1)
assert.Equal(t, engineResp.PolicyResponse.Rules[0].Status, response.RuleStatusPass, engineResp.PolicyResponse.Rules[0].Message)
}

func Test_SignaturesMultiKeyZeroGoodKey(t *testing.T) {
Expand Down
16 changes: 10 additions & 6 deletions pkg/utils/controller/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"time"

"github.com/go-logr/logr"
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
Expand All @@ -26,16 +27,19 @@ func AddEventHandlers(informer cache.SharedInformer, a addFunc, u updateFunc, d
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: a,
UpdateFunc: u,
DeleteFunc: d,
DeleteFunc: func(obj interface{}) {
d(kubeutils.GetObjectWithTombstone(obj))
},
})
}

func AddEventHandlersT[T any](informer cache.SharedInformer, a addFuncT[T], u updateFuncT[T], d deleteFuncT[T]) {
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { a(obj.(T)) },
UpdateFunc: func(old, obj interface{}) { u(old.(T), obj.(T)) },
DeleteFunc: func(obj interface{}) { d(obj.(T)) },
})
AddEventHandlers(
informer,
func(obj interface{}) { a(obj.(T)) },
func(old, obj interface{}) { u(old.(T), obj.(T)) },
func(obj interface{}) { d(obj.(T)) },
)
}

func AddKeyedEventHandlers(logger logr.Logger, informer cache.SharedInformer, queue workqueue.RateLimitingInterface, parseKey keyFunc) EnqueueFunc {
Expand Down