1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00
kyverno/pkg/engine/mutate/mutation.go
Khaled Emara 65a43d2059
feat(mutate): minimize unmarshals (#10702)
* feat(mutate): minimize unmarshals

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* test(mutate): test type assertion

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

* chore(codegen): remove unused import

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>

---------

Signed-off-by: Khaled Emara <khaled.emara@nirmata.com>
2024-08-09 11:12:20 +00:00

128 lines
4.4 KiB
Go

package mutate
import (
"fmt"
"strings"
"github.com/go-logr/logr"
kyvernov1 "github.com/kyverno/kyverno/api/kyverno/v1"
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
"github.com/kyverno/kyverno/pkg/engine/context"
"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
"github.com/kyverno/kyverno/pkg/engine/variables"
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
type Response struct {
Status engineapi.RuleStatus
PatchedResource unstructured.Unstructured
Message string
}
func NewResponse(status engineapi.RuleStatus, resource unstructured.Unstructured, msg string) *Response {
return &Response{
Status: status,
PatchedResource: resource,
Message: msg,
}
}
func NewErrorResponse(msg string, err error) *Response {
if err != nil {
msg = fmt.Sprintf("%s: %v", msg, err)
}
return NewResponse(engineapi.RuleStatusError, unstructured.Unstructured{}, msg)
}
func Mutate(rule *kyvernov1.Rule, ctx context.Interface, resource unstructured.Unstructured, logger logr.Logger) *Response {
updatedRule, err := variables.SubstituteAllInRule(logger, ctx, *rule)
if err != nil {
return NewErrorResponse("variable substitution failed", err)
}
m := updatedRule.Mutation
patcher := NewPatcher(m.GetPatchStrategicMerge(), m.PatchesJSON6902)
if patcher == nil {
return NewErrorResponse("empty mutate rule", nil)
}
patchedResource := resource.DeepCopy()
resourceBytes, err := patchedResource.MarshalJSON()
if err != nil {
return NewErrorResponse("failed to marshal resource", err)
}
patchedBytes, err := patcher.Patch(logger, resourceBytes)
if err != nil {
return NewErrorResponse("failed to patch resource", err)
}
if strings.TrimSpace(string(resourceBytes)) == strings.TrimSpace(string(patchedBytes)) {
return NewResponse(engineapi.RuleStatusSkip, resource, "no patches applied")
}
if err := patchedResource.UnmarshalJSON(patchedBytes); err != nil {
return NewErrorResponse("failed to unmarshal patched resource", err)
}
if rule.HasMutateExisting() {
if err := ctx.SetTargetResource(patchedResource.Object); err != nil {
return NewErrorResponse("failed to update patched target resource in the JSON context", err)
}
}
return NewResponse(engineapi.RuleStatusPass, *patchedResource, "resource patched")
}
func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engineapi.PolicyContext, resource unstructured.Unstructured, element interface{}, logger logr.Logger) *Response {
ctx := policyContext.JSONContext()
fe, err := substituteAllInForEach(foreach, ctx, logger)
if err != nil {
return NewErrorResponse("variable substitution failed", err)
}
patcher := NewPatcher(fe["patchStrategicMerge"], fe["patchesJson6902"].(string))
if patcher == nil {
return NewErrorResponse("empty mutate rule", nil)
}
patchedResource := resource.DeepCopy()
resourceBytes, err := patchedResource.MarshalJSON()
if err != nil {
return NewErrorResponse("failed to marshal resource", err)
}
patchedBytes, err := patcher.Patch(logger, resourceBytes)
if err != nil {
return NewErrorResponse("failed to patch resource", err)
}
if strings.TrimSpace(string(resourceBytes)) == strings.TrimSpace(string(patchedBytes)) {
return NewResponse(engineapi.RuleStatusSkip, resource, "no patches applied")
}
if err := patchedResource.UnmarshalJSON(patchedBytes); err != nil {
return NewErrorResponse("failed to unmarshal patched resource", err)
}
return NewResponse(engineapi.RuleStatusPass, *patchedResource, "resource patched")
}
func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (map[string]interface{}, error) {
patchesMap := make(map[string]interface{})
patchesMap["patchStrategicMerge"] = fe.GetPatchStrategicMerge()
patchesMap["patchesJson6902"] = fe.PatchesJSON6902
subedPatchesMap, err := variables.SubstituteAll(logger, ctx, patchesMap)
if err != nil {
return nil, err
}
typedMap, ok := subedPatchesMap.(map[string]interface{})
if !ok {
return nil, fmt.Errorf("failed to convert patched map to map[string]interface{}")
}
return typedMap, nil
}
func NewPatcher(strategicMergePatch apiextensions.JSON, jsonPatch string) patch.Patcher {
if strategicMergePatch != nil {
return patch.NewPatchStrategicMerge(strategicMergePatch)
}
if len(jsonPatch) > 0 {
return patch.NewPatchesJSON6902(jsonPatch)
}
return nil
}