1
0
Fork 0
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:
Charles-Edouard Brétéché 2023-04-28 09:31:12 +02:00 committed by GitHub
parent 6ffe013236
commit 5dd5b57f6c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 128 additions and 214 deletions

View file

@ -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

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)
}
}
}

View file

@ -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)
}

View file

@ -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

View file

@ -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) {

View file

@ -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) {