1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00
kyverno/pkg/background/mutate/mutate.go
shuting 481798c836
refactor: update updaterequest to be created for each policy (#10793)
* chore: remove v1beta1 updaterequest definitions

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: update UR to map a policy instead a rule; adapt UR mapping changes for admission review

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: update code-gen

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: linter

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: remove unused function

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: add missing files

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: add missing files

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update ur in policy controller

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: update crds

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: adapt ur changes in the background controller

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: linter

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: more linter

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: modify mapping relationship for deletion events

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: remedy missing target for policy application

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: fetching logic for triggers

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: clean up targets upon policy deletion

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update crds

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* merge main

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* merge main

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: adds delay before assertion

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* chore: update docs

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: wrong yaml format

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* feat: update error handling logic

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix(attempt): enable more debug info

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix(attempt): enable debug log

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix(attempt): enable debug log

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix(attempt): enable debug log

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: makefile to update ur crds

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: generate existing

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: skip empty ur generation

Signed-off-by: ShutingZhao <shuting@nirmata.com>

* fix: update install.yaml

Signed-off-by: ShutingZhao <shuting@nirmata.com>

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
2024-08-13 17:14:06 +00:00

257 lines
9.3 KiB
Go

package mutate
import (
"context"
"fmt"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernov2 "github.com/kyverno/kyverno/api/kyverno/v2"
"github.com/kyverno/kyverno/pkg/background/common"
"github.com/kyverno/kyverno/pkg/client/clientset/versioned"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/jmespath"
"github.com/kyverno/kyverno/pkg/event"
admissionutils "github.com/kyverno/kyverno/pkg/utils/admission"
engineutils "github.com/kyverno/kyverno/pkg/utils/engine"
"go.uber.org/multierr"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/client-go/tools/cache"
)
var ErrEmptyPatch error = fmt.Errorf("empty resource to patch")
type mutateExistingController struct {
// clients
client dclient.Interface
kyvernoClient versioned.Interface
statusControl common.StatusControlInterface
engine engineapi.Engine
// listers
policyLister kyvernov1listers.ClusterPolicyLister
npolicyLister kyvernov1listers.PolicyLister
nsLister corev1listers.NamespaceLister
configuration config.Configuration
eventGen event.Interface
log logr.Logger
jp jmespath.Interface
}
// NewMutateExistingController returns an instance of the MutateExistingController
func NewMutateExistingController(
client dclient.Interface,
kyvernoClient versioned.Interface,
statusControl common.StatusControlInterface,
engine engineapi.Engine,
policyLister kyvernov1listers.ClusterPolicyLister,
npolicyLister kyvernov1listers.PolicyLister,
nsLister corev1listers.NamespaceLister,
dynamicConfig config.Configuration,
eventGen event.Interface,
log logr.Logger,
jp jmespath.Interface,
) *mutateExistingController {
c := mutateExistingController{
client: client,
kyvernoClient: kyvernoClient,
statusControl: statusControl,
engine: engine,
policyLister: policyLister,
npolicyLister: npolicyLister,
nsLister: nsLister,
configuration: dynamicConfig,
eventGen: eventGen,
log: log,
jp: jp,
}
return &c
}
func (c *mutateExistingController) ProcessUR(ur *kyvernov2.UpdateRequest) error {
logger := c.log.WithValues("name", ur.GetName(), "policy", ur.Spec.GetPolicyKey(), "resource", ur.Spec.GetResource().String())
var errs []error
policy, err := c.getPolicy(ur)
if err != nil {
logger.Error(err, "failed to get policy")
return err
}
for _, rule := range policy.GetSpec().Rules {
if !rule.HasMutateExisting() || ur.Spec.Rule != rule.Name {
continue
}
var trigger *unstructured.Unstructured
admissionRequest := ur.Spec.Context.AdmissionRequestInfo.AdmissionRequest
if admissionRequest == nil {
trigger, err = common.GetResource(c.client, ur.Spec.Resource, ur.Spec, c.log)
if err != nil || trigger == nil {
logger.WithName(rule.Name).Error(err, "failed to get trigger resource")
if err := updateURStatus(c.statusControl, *ur, err); err != nil {
return err
}
continue
}
} else {
if admissionRequest.Operation == admissionv1.Create {
trigger, err = common.GetResource(c.client, ur.Spec.Resource, ur.Spec, c.log)
if err != nil || trigger == nil {
if admissionRequest.SubResource == "" {
logger.WithName(rule.Name).Error(err, "failed to get trigger resource")
if err := updateURStatus(c.statusControl, *ur, err); err != nil {
return err
}
continue
} else {
logger.WithName(rule.Name).Info("trigger resource not found for subresource, reverting to resource in AdmissionReviewRequest", "subresource", admissionRequest.SubResource)
newResource, _, err := admissionutils.ExtractResources(nil, *admissionRequest)
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to extract resources from admission review request")
errs = append(errs, err)
continue
}
trigger = &newResource
}
}
} else {
newResource, oldResource, err := admissionutils.ExtractResources(nil, *admissionRequest)
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to extract resources from admission review request")
errs = append(errs, err)
continue
}
trigger = &newResource
if newResource.Object == nil {
trigger = &oldResource
}
}
}
namespaceLabels := engineutils.GetNamespaceSelectorsFromNamespaceLister(trigger.GetKind(), trigger.GetNamespace(), c.nsLister, logger)
policyContext, err := common.NewBackgroundContext(logger, c.client, ur.Spec.Context, policy, trigger, c.configuration, c.jp, namespaceLabels)
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to build policy context")
errs = append(errs, err)
continue
}
if admissionRequest != nil {
var gvk schema.GroupVersionKind
gvk, err = c.client.Discovery().GetGVKFromGVR(schema.GroupVersionResource(admissionRequest.Resource))
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to get GVK from GVR", "GVR", admissionRequest.Resource)
errs = append(errs, err)
continue
}
policyContext = policyContext.WithResourceKind(gvk, admissionRequest.SubResource)
}
er := c.engine.Mutate(context.TODO(), policyContext)
for _, r := range er.PolicyResponse.Rules {
patched, parentGVR, patchedSubresource := r.PatchedTarget()
switch r.Status() {
case engineapi.RuleStatusFail, engineapi.RuleStatusError, engineapi.RuleStatusWarn:
err := fmt.Errorf("failed to mutate existing resource, rule %s, response %v: %s", r.Name(), r.Status(), r.Message())
logger.Error(err, "")
errs = append(errs, err)
c.report(err, policy, rule.Name, patched)
case engineapi.RuleStatusSkip:
err := fmt.Errorf("mutate existing rule skipped, rule %s, response %v: %s", r.Name(), r.Status(), r.Message())
logger.V(4).Info(err.Error())
case engineapi.RuleStatusPass:
patchedNew := patched
if patchedNew == nil {
logger.Error(ErrEmptyPatch, "", "rule", r.Name(), "message", r.Message())
errs = append(errs, ErrEmptyPatch)
continue
}
patchedNew.SetResourceVersion(patched.GetResourceVersion())
var updateErr error
if patchedSubresource == "status" {
_, updateErr = c.client.UpdateStatusResource(context.TODO(), patchedNew.GetAPIVersion(), patchedNew.GetKind(), patchedNew.GetNamespace(), patchedNew.Object, false)
} else if patchedSubresource != "" {
parentResourceGVR := parentGVR
parentResourceGV := schema.GroupVersion{Group: parentResourceGVR.Group, Version: parentResourceGVR.Version}
parentResourceGVK, err := c.client.Discovery().GetGVKFromGVR(parentResourceGV.WithResource(parentResourceGVR.Resource))
if err != nil {
logger.Error(err, "failed to get GVK from GVR", "GVR", parentResourceGVR)
errs = append(errs, err)
continue
}
_, updateErr = c.client.UpdateResource(context.TODO(), parentResourceGV.String(), parentResourceGVK.Kind, patchedNew.GetNamespace(), patchedNew.Object, false, patchedSubresource)
} else {
_, updateErr = c.client.UpdateResource(context.TODO(), patchedNew.GetAPIVersion(), patchedNew.GetKind(), patchedNew.GetNamespace(), patchedNew.Object, false)
}
if updateErr != nil {
errs = append(errs, updateErr)
logger.WithName(rule.Name).Error(updateErr, "failed to update target resource", "namespace", patchedNew.GetNamespace(), "name", patchedNew.GetName())
} else {
logger.WithName(rule.Name).V(4).Info("successfully mutated existing resource", "namespace", patchedNew.GetNamespace(), "name", patchedNew.GetName())
}
c.report(updateErr, policy, rule.Name, patched)
}
}
}
err = multierr.Combine(errs...)
return updateURStatus(c.statusControl, *ur, err)
}
func (c *mutateExistingController) getPolicy(ur *kyvernov2.UpdateRequest) (policy kyvernov1.PolicyInterface, err error) {
pNamespace, pName, err := cache.SplitMetaNamespaceKey(ur.Spec.Policy)
if err != nil {
return nil, err
}
if pNamespace != "" {
return c.npolicyLister.Policies(pNamespace).Get(pName)
}
return c.policyLister.Get(pName)
}
func (c *mutateExistingController) report(err error, policy kyvernov1.PolicyInterface, rule string, target *unstructured.Unstructured) {
var events []event.Info
if target == nil {
c.log.WithName("mutateExisting").Info("cannot generate events for empty target resource", "policy", policy.GetName(), "rule", rule)
return
}
if err != nil {
events = event.NewBackgroundFailedEvent(err, policy, rule, event.MutateExistingController,
kyvernov1.ResourceSpec{Kind: target.GetKind(), Namespace: target.GetNamespace(), Name: target.GetName()})
} else {
events = event.NewBackgroundSuccessEvent(event.MutateExistingController, policy,
[]kyvernov1.ResourceSpec{{Kind: target.GetKind(), Namespace: target.GetNamespace(), Name: target.GetName()}})
}
c.eventGen.Add(events...)
}
func updateURStatus(statusControl common.StatusControlInterface, ur kyvernov2.UpdateRequest, err error) error {
if err != nil {
if _, err := statusControl.Failed(ur.GetName(), err.Error(), nil); err != nil {
return err
}
} else {
if _, err := statusControl.Success(ur.GetName(), nil); err != nil {
return err
}
}
return nil
}