mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
67b96a7cf2
* 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>
257 lines
9.3 KiB
Go
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
|
|
}
|