mirror of
https://github.com/kyverno/kyverno.git
synced 2025-03-05 07:26:55 +00:00
refactor: engine patchers (#7030)
* refactor: engine patchers Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> * fix Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com> --------- Signed-off-by: Charles-Edouard Brétéché <charles.edouard@nirmata.com>
This commit is contained in:
parent
6ffe013236
commit
5dd5b57f6c
10 changed files with 128 additions and 214 deletions
|
@ -5,7 +5,6 @@ import (
|
|||
|
||||
"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/internal"
|
||||
"github.com/kyverno/kyverno/pkg/engine/mutate"
|
||||
|
@ -84,13 +83,19 @@ func applyForEachMutate(name string, foreach []kyvernov1.ForEachMutation, resour
|
|||
}
|
||||
|
||||
func applyPatches(name string, mergePatch apiextensions.JSON, jsonPatch string, resource unstructured.Unstructured, logger logr.Logger) (unstructured.Unstructured, error) {
|
||||
patcher := mutate.NewPatcher(name, mergePatch, jsonPatch, resource, logger)
|
||||
resp, mutatedResource := patcher.Patch()
|
||||
if resp.Status() != engineapi.RuleStatusPass {
|
||||
return mutatedResource, fmt.Errorf("mutate status %q: %s", resp.Status(), resp.Message())
|
||||
patcher := mutate.NewPatcher(mergePatch, jsonPatch)
|
||||
resourceBytes, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
|
||||
return mutatedResource, nil
|
||||
resourceBytes, _, err = patcher.Patch(logger, resourceBytes)
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
if err := resource.UnmarshalJSON(resourceBytes); err != nil {
|
||||
return resource, err
|
||||
}
|
||||
return resource, err
|
||||
}
|
||||
|
||||
// removeConditions mutates the rule to remove AnyAllConditions
|
||||
|
|
|
@ -2,7 +2,6 @@ package mutation
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
|
@ -29,7 +28,7 @@ type forEachMutator struct {
|
|||
|
||||
func (f *forEachMutator) mutateForEach(ctx context.Context) *mutate.Response {
|
||||
var applyCount int
|
||||
allPatches := make([][]byte, 0)
|
||||
var allPatches []jsonpatch.JsonPatchOperation
|
||||
|
||||
for _, foreach := range f.foreach {
|
||||
elements, err := engineutils.EvaluateList(foreach.List, f.policyContext.JSONContext())
|
||||
|
@ -69,7 +68,7 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
|
|||
defer f.policyContext.JSONContext().Restore()
|
||||
|
||||
patchedResource := f.resource
|
||||
var allPatches [][]byte
|
||||
var allPatches []jsonpatch.JsonPatchOperation
|
||||
if foreach.RawPatchStrategicMerge != nil {
|
||||
engineutils.InvertedElement(elements)
|
||||
}
|
||||
|
@ -132,24 +131,8 @@ func (f *forEachMutator) mutateElements(ctx context.Context, foreach kyvernov1.F
|
|||
allPatches = append(allPatches, mutateResp.Patches...)
|
||||
}
|
||||
}
|
||||
var sortedPatches []jsonpatch.JsonPatchOperation
|
||||
for _, p := range allPatches {
|
||||
var jp jsonpatch.JsonPatchOperation
|
||||
if err := json.Unmarshal(p, &jp); err != nil {
|
||||
return mutate.NewErrorResponse("failed to convert patch", err)
|
||||
}
|
||||
sortedPatches = append(sortedPatches, jp)
|
||||
}
|
||||
sortedPatches = patch.FilterAndSortPatches(sortedPatches)
|
||||
var finalPatches [][]byte
|
||||
for _, p := range sortedPatches {
|
||||
if data, err := p.MarshalJSON(); err != nil {
|
||||
return mutate.NewErrorResponse("failed to marshal patch", err)
|
||||
} else {
|
||||
finalPatches = append(finalPatches, data)
|
||||
}
|
||||
}
|
||||
return mutate.NewResponse(engineapi.RuleStatusPass, patchedResource.unstructured, finalPatches, "")
|
||||
sortedPatches := patch.FilterAndSortPatches(allPatches)
|
||||
return mutate.NewResponse(engineapi.RuleStatusPass, patchedResource.unstructured, sortedPatches, "")
|
||||
}
|
||||
|
||||
func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info resourceInfo) *engineapi.RuleResponse {
|
||||
|
@ -164,7 +147,7 @@ func buildRuleResponse(rule *kyvernov1.Rule, mutateResp *mutate.Response, info r
|
|||
mutateResp.Status,
|
||||
)
|
||||
if mutateResp.Status == engineapi.RuleStatusPass {
|
||||
resp = resp.WithPatches(mutateResp.Patches...)
|
||||
resp = resp.WithPatches(patch.ConvertPatches(mutateResp.Patches...)...)
|
||||
if len(rule.Mutation.Targets) != 0 {
|
||||
resp = resp.WithPatchedTarget(&mutateResp.PatchedResource, info.parentResourceGVR, info.subresource)
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
|
||||
"github.com/kyverno/kyverno/pkg/engine/variables"
|
||||
datautils "github.com/kyverno/kyverno/pkg/utils/data"
|
||||
"github.com/mattbaird/jsonpatch"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
@ -18,15 +19,11 @@ import (
|
|||
type Response struct {
|
||||
Status engineapi.RuleStatus
|
||||
PatchedResource unstructured.Unstructured
|
||||
Patches [][]byte
|
||||
Patches []jsonpatch.JsonPatchOperation
|
||||
Message string
|
||||
}
|
||||
|
||||
func NewErrorResponse(msg string, err error) *Response {
|
||||
return NewResponse(engineapi.RuleStatusError, unstructured.Unstructured{}, nil, fmt.Sprintf("%s: %v", msg, err))
|
||||
}
|
||||
|
||||
func NewResponse(status engineapi.RuleStatus, resource unstructured.Unstructured, patches [][]byte, msg string) *Response {
|
||||
func NewResponse(status engineapi.RuleStatus, resource unstructured.Unstructured, patches []jsonpatch.JsonPatchOperation, msg string) *Response {
|
||||
return &Response{
|
||||
Status: status,
|
||||
PatchedResource: resource,
|
||||
|
@ -35,38 +32,47 @@ func NewResponse(status engineapi.RuleStatus, resource unstructured.Unstructured
|
|||
}
|
||||
}
|
||||
|
||||
func NewErrorResponse(msg string, err error) *Response {
|
||||
if err != nil {
|
||||
msg = fmt.Sprintf("%s: %v", msg, err)
|
||||
}
|
||||
return NewResponse(engineapi.RuleStatusError, unstructured.Unstructured{}, nil, 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(updatedRule.Name, m.GetPatchStrategicMerge(), m.PatchesJSON6902, resource, logger)
|
||||
patcher := NewPatcher(m.GetPatchStrategicMerge(), m.PatchesJSON6902)
|
||||
if patcher == nil {
|
||||
return NewResponse(engineapi.RuleStatusError, resource, nil, "empty mutate rule")
|
||||
return NewErrorResponse("empty mutate rule", nil)
|
||||
}
|
||||
|
||||
resp, patchedResource := patcher.Patch()
|
||||
if resp.Status() != engineapi.RuleStatusPass {
|
||||
return NewResponse(resp.Status(), resource, nil, resp.Message())
|
||||
resourceBytes, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
return NewErrorResponse("failed to marshal resource", err)
|
||||
}
|
||||
|
||||
if resp.Patches() == nil {
|
||||
resourceBytes, patches, err := patcher.Patch(logger, resourceBytes)
|
||||
if err != nil {
|
||||
return NewErrorResponse("failed to patch resource", err)
|
||||
}
|
||||
if len(patches) == 0 {
|
||||
return NewResponse(engineapi.RuleStatusSkip, resource, nil, "no patches applied")
|
||||
}
|
||||
|
||||
if err := resource.UnmarshalJSON(resourceBytes); err != nil {
|
||||
return NewErrorResponse("failed to unmarshal patched resource", err)
|
||||
}
|
||||
if rule.IsMutateExisting() {
|
||||
if err := ctx.AddTargetResource(patchedResource.Object); err != nil {
|
||||
if err := ctx.AddTargetResource(resource.Object); err != nil {
|
||||
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||
}
|
||||
} else {
|
||||
if err := ctx.AddResource(patchedResource.Object); err != nil {
|
||||
if err := ctx.AddResource(resource.Object); err != nil {
|
||||
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||
}
|
||||
}
|
||||
|
||||
return NewResponse(engineapi.RuleStatusPass, patchedResource, resp.Patches(), resp.Message())
|
||||
return NewResponse(engineapi.RuleStatusPass, resource, patches, "resource patched")
|
||||
}
|
||||
|
||||
func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engineapi.PolicyContext, resource unstructured.Unstructured, element interface{}, logger logr.Logger) *Response {
|
||||
|
@ -75,26 +81,28 @@ func ForEach(name string, foreach kyvernov1.ForEachMutation, policyContext engin
|
|||
if err != nil {
|
||||
return NewErrorResponse("variable substitution failed", err)
|
||||
}
|
||||
|
||||
patcher := NewPatcher(name, fe.GetPatchStrategicMerge(), fe.PatchesJSON6902, resource, logger)
|
||||
patcher := NewPatcher(fe.GetPatchStrategicMerge(), fe.PatchesJSON6902)
|
||||
if patcher == nil {
|
||||
return NewResponse(engineapi.RuleStatusError, unstructured.Unstructured{}, nil, "no patches found")
|
||||
return NewErrorResponse("empty mutate rule", nil)
|
||||
}
|
||||
|
||||
resp, patchedResource := patcher.Patch()
|
||||
if resp.Status() != engineapi.RuleStatusPass {
|
||||
return NewResponse(resp.Status(), unstructured.Unstructured{}, nil, resp.Message())
|
||||
resourceBytes, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
return NewErrorResponse("failed to marshal resource", err)
|
||||
}
|
||||
|
||||
if resp.Patches() == nil {
|
||||
return NewResponse(engineapi.RuleStatusSkip, unstructured.Unstructured{}, nil, "no patches applied")
|
||||
resourceBytes, patches, err := patcher.Patch(logger, resourceBytes)
|
||||
if err != nil {
|
||||
return NewErrorResponse("failed to patch resource", err)
|
||||
}
|
||||
|
||||
if err := ctx.AddResource(patchedResource.Object); err != nil {
|
||||
if len(patches) == 0 {
|
||||
return NewResponse(engineapi.RuleStatusSkip, resource, nil, "no patches applied")
|
||||
}
|
||||
if err := resource.UnmarshalJSON(resourceBytes); err != nil {
|
||||
return NewErrorResponse("failed to unmarshal patched resource", err)
|
||||
} else if err := ctx.AddResource(resource.Object); err != nil {
|
||||
return NewErrorResponse("failed to update patched resource in the JSON context", err)
|
||||
} else {
|
||||
return NewResponse(engineapi.RuleStatusPass, resource, patches, "resource patched")
|
||||
}
|
||||
|
||||
return NewResponse(engineapi.RuleStatusPass, patchedResource, resp.Patches(), resp.Message())
|
||||
}
|
||||
|
||||
func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface, logger logr.Logger) (*kyvernov1.ForEachMutation, error) {
|
||||
|
@ -121,14 +129,12 @@ func substituteAllInForEach(fe kyvernov1.ForEachMutation, ctx context.Interface,
|
|||
return &updatedForEach, nil
|
||||
}
|
||||
|
||||
func NewPatcher(name string, strategicMergePatch apiextensions.JSON, jsonPatch string, r unstructured.Unstructured, logger logr.Logger) patch.Patcher {
|
||||
func NewPatcher(strategicMergePatch apiextensions.JSON, jsonPatch string) patch.Patcher {
|
||||
if strategicMergePatch != nil {
|
||||
return patch.NewPatchStrategicMerge(name, strategicMergePatch, r, logger)
|
||||
return patch.NewPatchStrategicMerge(strategicMergePatch)
|
||||
}
|
||||
|
||||
if len(jsonPatch) > 0 {
|
||||
return patch.NewPatchesJSON6902(name, jsonPatch, r, logger)
|
||||
return patch.NewPatchesJSON6902(jsonPatch)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"github.com/kyverno/kyverno/pkg/engine/context"
|
||||
"github.com/kyverno/kyverno/pkg/engine/jmespath"
|
||||
"github.com/kyverno/kyverno/pkg/engine/mutate/patch"
|
||||
kubeutils "github.com/kyverno/kyverno/pkg/utils/kube"
|
||||
"gotest.tools/assert"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
|
@ -53,7 +54,13 @@ func applyPatches(rule *types.Rule, resource unstructured.Unstructured) (*engine
|
|||
if mutateResp.Status != engineapi.RuleStatusPass {
|
||||
return engineapi.NewRuleResponse("", engineapi.Mutation, mutateResp.Message, mutateResp.Status), resource
|
||||
}
|
||||
return engineapi.RulePass("", engineapi.Mutation, mutateResp.Message).WithPatches(mutateResp.Patches...), mutateResp.PatchedResource
|
||||
return engineapi.RulePass(
|
||||
"",
|
||||
engineapi.Mutation,
|
||||
mutateResp.Message,
|
||||
).WithPatches(
|
||||
patch.ConvertPatches(mutateResp.Patches...)...,
|
||||
), mutateResp.PatchedResource
|
||||
}
|
||||
|
||||
func TestProcessPatches_EmptyPatches(t *testing.T) {
|
||||
|
@ -98,7 +105,7 @@ func makeRuleWithPatches(t *testing.T, patches []jsonPatch) *types.Rule {
|
|||
func TestProcessPatches_EmptyDocument(t *testing.T) {
|
||||
rule := makeRuleWithPatch(t, makeAddIsMutatedLabelPatch())
|
||||
rr, _ := applyPatches(rule, unstructured.Unstructured{})
|
||||
assert.Equal(t, rr.Status(), engineapi.RuleStatusFail)
|
||||
assert.Equal(t, rr.Status(), engineapi.RuleStatusError)
|
||||
assert.Assert(t, len(rr.Patches()) == 0)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,52 +2,22 @@ package patch
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch/v5"
|
||||
"github.com/go-logr/logr"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
// ProcessPatchJSON6902 ...
|
||||
func ProcessPatchJSON6902(ruleName string, patchesJSON6902 []byte, resource unstructured.Unstructured, log logr.Logger) (engineapi.RuleResponse, unstructured.Unstructured) {
|
||||
logger := log.WithValues("rule", ruleName)
|
||||
startTime := time.Now()
|
||||
logger.V(4).Info("started JSON6902 patch", "startTime", startTime)
|
||||
defer func() {
|
||||
logger.V(4).Info("applied JSON6902 patch", "processingTime", time.Since(startTime))
|
||||
}()
|
||||
resourceRaw, err := resource.MarshalJSON()
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to marshal resource")
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to marshal resource: %v", err)), resource
|
||||
}
|
||||
patchedResourceRaw, err := applyPatchesWithOptions(resourceRaw, patchesJSON6902)
|
||||
if err != nil {
|
||||
func ProcessPatchJSON6902(logger logr.Logger, patchesJSON6902 []byte, resource resource) (resource, patches, error) {
|
||||
if patchedResourceRaw, err := applyPatchesWithOptions(resource, patchesJSON6902); err != nil {
|
||||
logger.Error(err, "failed to apply JSON Patch")
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to apply JSON Patch: %v", err)), resource
|
||||
return nil, nil, err
|
||||
} else if patchesBytes, err := generatePatches(resource, patchedResourceRaw); err != nil {
|
||||
return nil, nil, err
|
||||
} else {
|
||||
return patchedResourceRaw, patchesBytes, nil
|
||||
}
|
||||
patchesBytes, err := generatePatches(resourceRaw, patchedResourceRaw)
|
||||
if err != nil {
|
||||
logger.Error(err, "unable generate patch bytes from base and patched document, apply patchesJSON6902 directly")
|
||||
return *engineapi.RuleFail(
|
||||
ruleName,
|
||||
engineapi.Mutation,
|
||||
fmt.Sprintf("unable generate patch bytes from base and patched document, apply patchesJSON6902 directly: %v", err),
|
||||
), resource
|
||||
}
|
||||
for _, p := range patchesBytes {
|
||||
log.V(4).Info("generated JSON Patch (RFC 6902)", "patch", string(p))
|
||||
}
|
||||
var patchedResource unstructured.Unstructured
|
||||
err = patchedResource.UnmarshalJSON(patchedResourceRaw)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to unmarshal resource")
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to unmarshal resource: %v", err)), resource
|
||||
}
|
||||
return *engineapi.RulePass(ruleName, engineapi.Mutation, "applied JSON Patch").WithPatches(patchesBytes...), patchedResource
|
||||
}
|
||||
|
||||
func applyPatchesWithOptions(resource, patch []byte) ([]byte, error) {
|
||||
|
@ -55,13 +25,11 @@ func applyPatchesWithOptions(resource, patch []byte) ([]byte, error) {
|
|||
if err != nil {
|
||||
return resource, fmt.Errorf("failed to decode patches: %v", err)
|
||||
}
|
||||
|
||||
options := &jsonpatch.ApplyOptions{SupportNegativeIndices: true, AllowMissingPathOnRemove: true, EnsurePathExistsOnAdd: true}
|
||||
patchedResource, err := patches.ApplyWithOptions(resource, options)
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
|
||||
return patchedResource, nil
|
||||
}
|
||||
|
||||
|
@ -69,7 +37,6 @@ func ConvertPatchesToJSON(patchesJSON6902 string) ([]byte, error) {
|
|||
if len(patchesJSON6902) == 0 {
|
||||
return []byte(patchesJSON6902), nil
|
||||
}
|
||||
|
||||
if patchesJSON6902[0] != '[' {
|
||||
// If the patch doesn't look like a JSON6902 patch, we
|
||||
// try to parse it to json.
|
||||
|
@ -79,6 +46,5 @@ func ConvertPatchesToJSON(patchesJSON6902 string) ([]byte, error) {
|
|||
}
|
||||
return op, nil
|
||||
}
|
||||
|
||||
return []byte(patchesJSON6902), nil
|
||||
}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package patch
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
"github.com/go-logr/logr"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
assert "github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
@ -50,13 +48,14 @@ func TestTypeConversion(t *testing.T) {
|
|||
jsonPatches, err := yaml.YAMLToJSON(patchesJSON6902)
|
||||
assert.Nil(t, err)
|
||||
// apply patches
|
||||
resp, _ := ProcessPatchJSON6902("type-conversion", jsonPatches, resource, logr.Discard())
|
||||
if !assert.Equal(t, engineapi.RuleStatusPass, resp.Status()) {
|
||||
t.Fatal(resp.Message())
|
||||
resourceBytes, err := resource.MarshalJSON()
|
||||
assert.Nil(t, err)
|
||||
resourceBytes, patches, err := ProcessPatchJSON6902(logr.Discard(), jsonPatches, resourceBytes)
|
||||
if !assert.Nil(t, err) {
|
||||
t.Fatal()
|
||||
}
|
||||
|
||||
assert.Equal(t, expectedPatches, resp.Patches(),
|
||||
fmt.Sprintf("expectedPatches: %s\ngeneratedPatches: %s", string(expectedPatches[0]), string(resp.Patches()[0])))
|
||||
assert.Equal(t, expectedPatches, ConvertPatches(patches...))
|
||||
}
|
||||
|
||||
func TestJsonPatch(t *testing.T) {
|
||||
|
@ -218,7 +217,7 @@ spec:
|
|||
`,
|
||||
patches: `
|
||||
- path: "/spec/nodeSelector"
|
||||
op: add
|
||||
op: add
|
||||
value: {"node.kubernetes.io/role": "test"}
|
||||
`,
|
||||
expectedPatches: map[string]bool{
|
||||
|
@ -370,8 +369,7 @@ spec:
|
|||
assert.Nil(t, err)
|
||||
|
||||
for _, p := range generatedP {
|
||||
assert.Equal(t, test.expectedPatches[string(p)], true,
|
||||
fmt.Sprintf("test: %s\nunexpected patch: %s\nexpect one of: %v", test.name, string(p), test.expectedPatches))
|
||||
assert.Equal(t, test.expectedPatches[string(p.Json())], true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,65 +2,51 @@ package patch
|
|||
|
||||
import (
|
||||
"github.com/go-logr/logr"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"github.com/mattbaird/jsonpatch"
|
||||
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
type (
|
||||
resource = []byte
|
||||
patches = []jsonpatch.JsonPatchOperation
|
||||
)
|
||||
|
||||
// Patcher patches the resource
|
||||
type Patcher interface {
|
||||
Patch() (resp engineapi.RuleResponse, newPatchedResource unstructured.Unstructured)
|
||||
Patch(logr.Logger, resource) (resource, patches, error)
|
||||
}
|
||||
|
||||
// patchStrategicMergeHandler
|
||||
type patchStrategicMergeHandler struct {
|
||||
ruleName string
|
||||
patch apiextensions.JSON
|
||||
patchedResource unstructured.Unstructured
|
||||
logger logr.Logger
|
||||
patch apiextensions.JSON
|
||||
}
|
||||
|
||||
func NewPatchStrategicMerge(ruleName string, patch apiextensions.JSON, patchedResource unstructured.Unstructured, logger logr.Logger) Patcher {
|
||||
func NewPatchStrategicMerge(patch apiextensions.JSON) Patcher {
|
||||
return patchStrategicMergeHandler{
|
||||
ruleName: ruleName,
|
||||
patch: patch,
|
||||
patchedResource: patchedResource,
|
||||
logger: logger,
|
||||
patch: patch,
|
||||
}
|
||||
}
|
||||
|
||||
func (h patchStrategicMergeHandler) Patch() (engineapi.RuleResponse, unstructured.Unstructured) {
|
||||
return ProcessStrategicMergePatch(h.ruleName, h.patch, h.patchedResource, h.logger)
|
||||
func (h patchStrategicMergeHandler) Patch(logger logr.Logger, resource resource) (resource, patches, error) {
|
||||
return ProcessStrategicMergePatch(logger, h.patch, resource)
|
||||
}
|
||||
|
||||
// patchesJSON6902Handler
|
||||
type patchesJSON6902Handler struct {
|
||||
ruleName string
|
||||
patches string
|
||||
patchedResource unstructured.Unstructured
|
||||
logger logr.Logger
|
||||
patches string
|
||||
}
|
||||
|
||||
func NewPatchesJSON6902(ruleName string, patches string, patchedResource unstructured.Unstructured, logger logr.Logger) Patcher {
|
||||
func NewPatchesJSON6902(patches string) Patcher {
|
||||
return patchesJSON6902Handler{
|
||||
ruleName: ruleName,
|
||||
patches: patches,
|
||||
patchedResource: patchedResource,
|
||||
logger: logger,
|
||||
patches: patches,
|
||||
}
|
||||
}
|
||||
|
||||
func (h patchesJSON6902Handler) Patch() (engineapi.RuleResponse, unstructured.Unstructured) {
|
||||
func (h patchesJSON6902Handler) Patch(logger logr.Logger, resource resource) (resource, patches, error) {
|
||||
patchesJSON6902, err := ConvertPatchesToJSON(h.patches)
|
||||
if err != nil {
|
||||
resp := engineapi.RuleFail(
|
||||
h.ruleName,
|
||||
engineapi.Mutation,
|
||||
err.Error(),
|
||||
)
|
||||
h.logger.Error(err, "error in type conversion")
|
||||
return *resp, unstructured.Unstructured{}
|
||||
logger.Error(err, "error in type conversion")
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return ProcessPatchJSON6902(h.ruleName, patchesJSON6902, h.patchedResource, h.logger)
|
||||
return ProcessPatchJSON6902(logger, patchesJSON6902, resource)
|
||||
}
|
||||
|
|
|
@ -9,24 +9,22 @@ import (
|
|||
"github.com/mattbaird/jsonpatch"
|
||||
)
|
||||
|
||||
func generatePatches(src, dst []byte) ([][]byte, error) {
|
||||
var patchesBytes [][]byte
|
||||
pp, err := jsonpatch.CreatePatch(src, dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sortedPatches := FilterAndSortPatches(pp)
|
||||
for _, p := range sortedPatches {
|
||||
pbytes, err := p.MarshalJSON()
|
||||
if err != nil {
|
||||
return patchesBytes, err
|
||||
func ConvertPatches(in ...jsonpatch.JsonPatchOperation) [][]byte {
|
||||
var out [][]byte
|
||||
for _, patch := range in {
|
||||
if patch, err := patch.MarshalJSON(); err == nil {
|
||||
out = append(out, patch)
|
||||
}
|
||||
|
||||
patchesBytes = append(patchesBytes, pbytes)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
return patchesBytes, err
|
||||
func generatePatches(src, dst []byte) ([]jsonpatch.JsonPatchOperation, error) {
|
||||
if pp, err := jsonpatch.CreatePatch(src, dst); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return FilterAndSortPatches(pp), err
|
||||
}
|
||||
}
|
||||
|
||||
// FilterAndSortPatches
|
||||
|
|
|
@ -24,7 +24,7 @@ func Test_GeneratePatches(t *testing.T) {
|
|||
assert.NilError(t, err)
|
||||
|
||||
for _, p := range patches {
|
||||
assertnew.Equal(t, expectedPatches[string(p)], true)
|
||||
assertnew.Equal(t, expectedPatches[p.Json()], true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -223,7 +223,7 @@ func Test_GeneratePatches_sortRemovalPatches(t *testing.T) {
|
|||
patches, err := generatePatches(base, patchedResource)
|
||||
fmt.Println(patches)
|
||||
assertnew.Nil(t, err)
|
||||
assertnew.Equal(t, expectedPatches, patches)
|
||||
assertnew.Equal(t, expectedPatches, ConvertPatches(patches...))
|
||||
}
|
||||
|
||||
func Test_sortRemovalPatches(t *testing.T) {
|
||||
|
|
|
@ -4,65 +4,30 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
engineapi "github.com/kyverno/kyverno/pkg/engine/api"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"sigs.k8s.io/kustomize/api/filters/patchstrategicmerge"
|
||||
filtersutil "sigs.k8s.io/kustomize/kyaml/filtersutil"
|
||||
yaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||
)
|
||||
|
||||
// ProcessStrategicMergePatch ...
|
||||
func ProcessStrategicMergePatch(ruleName string, overlay interface{}, resource unstructured.Unstructured, log logr.Logger) (engineapi.RuleResponse, unstructured.Unstructured) {
|
||||
startTime := time.Now()
|
||||
logger := log.WithName("ProcessStrategicMergePatch").WithValues("rule", ruleName)
|
||||
logger.V(4).Info("started applying strategicMerge patch", "startTime", startTime)
|
||||
|
||||
defer func() {
|
||||
logger.V(4).Info("finished applying strategicMerge patch", "processingTime", time.Since(startTime))
|
||||
}()
|
||||
|
||||
func ProcessStrategicMergePatch(logger logr.Logger, overlay interface{}, resource resource) (resource, patches, error) {
|
||||
overlayBytes, err := json.Marshal(overlay)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to marshal resource")
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to process patchStrategicMerge: %v", err)), resource
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
base, err := json.Marshal(resource.Object)
|
||||
patchedBytes, err := strategicMergePatch(logger, string(resource), string(overlayBytes))
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to marshal resource")
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to process patchStrategicMerge: %v", err)), resource
|
||||
logger.Error(err, "failed to apply patchStrategicMerge")
|
||||
return nil, nil, err
|
||||
}
|
||||
patchedBytes, err := strategicMergePatch(logger, string(base), string(overlayBytes))
|
||||
patches, err := generatePatches(resource, patchedBytes)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to apply patchStrategicMerge")
|
||||
msg := fmt.Sprintf("failed to apply patchStrategicMerge: %v", err)
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, msg), resource
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var patchedResource unstructured.Unstructured
|
||||
err = patchedResource.UnmarshalJSON(patchedBytes)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to unmarshal resource")
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, fmt.Sprintf("failed to process patchStrategicMerge: %v", err)), resource
|
||||
}
|
||||
|
||||
log.V(6).Info("generating JSON patches from patched resource", "patchedResource", patchedResource.Object)
|
||||
|
||||
jsonPatches, err := generatePatches(base, patchedBytes)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("failed to generated JSON patches from patched resource: %v", err.Error())
|
||||
log.V(2).Info(msg)
|
||||
return *engineapi.RuleFail(ruleName, engineapi.Mutation, msg), patchedResource
|
||||
}
|
||||
|
||||
for _, p := range jsonPatches {
|
||||
log.V(5).Info("generated patch", "patch", string(p))
|
||||
}
|
||||
|
||||
return *engineapi.RulePass(ruleName, engineapi.Mutation, "applied strategic merge patch").WithPatches(jsonPatches...), patchedResource
|
||||
return patchedBytes, patches, nil
|
||||
}
|
||||
|
||||
func strategicMergePatch(logger logr.Logger, base, overlay string) ([]byte, error) {
|
||||
|
|
Loading…
Add table
Reference in a new issue