1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00
kyverno/pkg/background/mutate/mutate.go
shuting 67b96a7cf2
refactor: mutate checks (#9255)
* refactor

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

* fix

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

* fix

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

---------

Signed-off-by: ShutingZhao <shuting@nirmata.com>
2023-12-22 15:07:17 +02: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"
kyvernov1beta1 "github.com/kyverno/kyverno/api/kyverno/v1beta1"
"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 *kyvernov1beta1.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, 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, 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, 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.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 *kyvernov1beta1.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 kyvernov1beta1.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
}