1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/background/mutate/mutate.go
shuting 3bf3dcc1af
Add the metric "kyverno_client_queries_total" (#4359)
* Add metric "kyverno_kube_client_queries_total"

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

* publish metric for missing queries

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

* Refactor the way Kyverno registers QPS metric

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

* Move clientsets to a dedicated folder

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

* Wrap Kyverno client and policyreport client to register client query metric

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

* address linter comments

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

* address linter comments

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

* Switch to use wrapper clients

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

Signed-off-by: ShutingZhao <shuting@nirmata.com>
Co-authored-by: Vyankatesh Kudtarkar <vyankateshkd@gmail.com>
2022-08-31 11:33:47 +05:30

232 lines
6.9 KiB
Go

package mutate
import (
"encoding/json"
"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"
kyvernov1listers "github.com/kyverno/kyverno/pkg/client/listers/kyverno/v1"
"github.com/kyverno/kyverno/pkg/clients/dclient"
"github.com/kyverno/kyverno/pkg/config"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
"github.com/kyverno/kyverno/pkg/event"
"github.com/kyverno/kyverno/pkg/utils"
"go.uber.org/multierr"
yamlv2 "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
cache "k8s.io/client-go/tools/cache"
)
var ErrEmptyPatch error = fmt.Errorf("empty resource to patch")
type MutateExistingController struct {
// clients
client dclient.Interface
statusControl common.StatusControlInterface
// listers
policyLister kyvernov1listers.ClusterPolicyLister
npolicyLister kyvernov1listers.PolicyLister
configuration config.Configuration
eventGen event.Interface
log logr.Logger
}
// NewMutateExistingController returns an instance of the MutateExistingController
func NewMutateExistingController(
client dclient.Interface,
statusControl common.StatusControlInterface,
policyLister kyvernov1listers.ClusterPolicyLister,
npolicyLister kyvernov1listers.PolicyLister,
dynamicConfig config.Configuration,
eventGen event.Interface,
log logr.Logger,
) *MutateExistingController {
c := MutateExistingController{
client: client,
statusControl: statusControl,
policyLister: policyLister,
npolicyLister: npolicyLister,
configuration: dynamicConfig,
eventGen: eventGen,
log: log,
}
return &c
}
func (c *MutateExistingController) ProcessUR(ur *kyvernov1beta1.UpdateRequest) error {
logger := c.log.WithValues("name", ur.Name, "policy", ur.Spec.Policy, "kind", ur.Spec.Resource.Kind, "apiVersion", ur.Spec.Resource.APIVersion, "namespace", ur.Spec.Resource.Namespace, "name", ur.Spec.Resource.Name)
var errs []error
policy, err := c.getPolicy(ur.Spec.Policy)
if err != nil {
logger.Error(err, "failed to get policy")
return err
}
for _, rule := range policy.GetSpec().Rules {
if !rule.IsMutateExisting() {
continue
}
trigger, err := common.GetResource(c.client, ur.Spec, c.log)
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to get trigger resource")
errs = append(errs, err)
continue
}
policyContext, _, err := common.NewBackgroundContext(c.client, ur, policy, trigger, c.configuration, nil, logger)
if err != nil {
logger.WithName(rule.Name).Error(err, "failed to build policy context")
errs = append(errs, err)
continue
}
er := engine.Mutate(policyContext)
for _, r := range er.PolicyResponse.Rules {
patched := r.PatchedTarget
switch r.Status {
case response.RuleStatusFail, response.RuleStatusError, response.RuleStatusWarn:
err := fmt.Errorf("failed to mutate existing resource, rule response%v: %s", r.Status, r.Message)
logger.Error(err, "")
errs = append(errs, err)
c.report(err, ur.Spec.Policy, rule.Name, patched)
case response.RuleStatusSkip:
logger.Info("mutate existing rule skipped", "rule", r.Name, "message", r.Message)
c.report(err, ur.Spec.Policy, rule.Name, patched)
case response.RuleStatusPass:
patchedNew, err := addAnnotation(policy, patched, r)
if err != nil {
logger.Error(err, "failed to apply patches")
errs = append(errs, err)
}
if patchedNew == nil {
logger.Error(ErrEmptyPatch, "", "rule", r.Name, "message", r.Message)
errs = append(errs, err)
continue
}
if r.Status == response.RuleStatusPass {
_, updateErr := c.client.UpdateResource(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, ur.Spec.Policy, rule.Name, patched)
}
}
}
}
err = multierr.Combine(errs...)
return updateURStatus(c.statusControl, *ur, err)
}
func (c *MutateExistingController) getPolicy(key string) (kyvernov1.PolicyInterface, error) {
pNamespace, pName, err := cache.SplitMetaNamespaceKey(key)
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, 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, "rule", rule, "err", err.Error())
}
if err != nil {
events = event.NewBackgroundFailedEvent(err, policy, rule, event.MutateExistingController, target)
} else {
events = event.NewBackgroundSuccessEvent(policy, rule, event.MutateExistingController, target)
}
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
}
func addAnnotation(policy kyvernov1.PolicyInterface, patched *unstructured.Unstructured, r response.RuleResponse) (patchedNew *unstructured.Unstructured, err error) {
if patched == nil {
return
}
patchedNew = patched
var rulePatches []utils.RulePatch
for _, patch := range r.Patches {
var patchmap map[string]interface{}
if err := json.Unmarshal(patch, &patchmap); err != nil {
return nil, fmt.Errorf("failed to parse JSON patch bytes: %v", err)
}
rp := struct {
RuleName string `json:"rulename"`
Op string `json:"op"`
Path string `json:"path"`
}{
RuleName: r.Name,
Op: patchmap["op"].(string),
Path: patchmap["path"].(string),
}
rulePatches = append(rulePatches, rp)
}
annotationContent := make(map[string]string)
policyName := policy.GetName()
if policy.GetNamespace() != "" {
policyName = policy.GetNamespace() + "/" + policy.GetName()
}
for _, rulePatch := range rulePatches {
annotationContent[rulePatch.RuleName+"."+policyName+".kyverno.io"] = utils.OperationToPastTense[rulePatch.Op] + " " + rulePatch.Path
}
if len(annotationContent) == 0 {
return
}
result, _ := yamlv2.Marshal(annotationContent)
ann := patchedNew.GetAnnotations()
if ann == nil {
ann = make(map[string]string)
}
ann[utils.PolicyAnnotation] = string(result)
patchedNew.SetAnnotations(ann)
return
}