1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-29 02:45:06 +00:00

Fix merging JSON patches (#4202)

* fix merge of image verify and mutate patches

Signed-off-by: Jim Bugwadia <jim@nirmata.com>

* update json patch merge logic

Signed-off-by: Jim Bugwadia <jim@nirmata.com>
This commit is contained in:
Jim Bugwadia 2022-07-10 20:56:31 -07:00 committed by GitHub
parent 0712022640
commit 58337716c8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 106 additions and 32 deletions

View file

@ -178,7 +178,7 @@ func GenerateRulePatches(spec *kyvernov1.Spec, controllers string) (rulePatches
operation = "replace"
patchPostion = existingIndex
}
patch := jsonutils.NewPatch(fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)), operation, genRule)
patch := jsonutils.NewPatchOperation(fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)), operation, genRule)
pbytes, err := patch.Marshal()
if err != nil {
errs = append(errs, err)

View file

@ -135,7 +135,7 @@ func extractPatchPath(patches [][]byte, log logr.Logger) string {
var resultPath []string
// extract the patch path and value
for _, patch := range patches {
if data, err := jsonutils.UnmarshalPatch(patch); err != nil {
if data, err := jsonutils.UnmarshalPatchOperation(patch); err != nil {
log.Error(err, "failed to decode the generate patch", "patch", string(patch))
continue
} else {

View file

@ -56,7 +56,7 @@ func defaultBackgroundFlag(spec *kyvernov1.Spec, log logr.Logger) ([]byte, strin
if spec.Background == nil {
defaultVal := true
log.V(4).Info("setting default value", "spec.background", true)
patchByte, err := jsonutils.MarshalPatch("/spec/background", "add", &defaultVal)
patchByte, err := jsonutils.MarshalPatchOperation("/spec/background", "add", &defaultVal)
if err != nil {
log.Error(err, "failed to set default value", "spec.background", true)
return nil, ""
@ -72,7 +72,7 @@ func defaultvalidationFailureAction(spec *kyvernov1.Spec, log logr.Logger) ([]by
if spec.ValidationFailureAction == "" {
audit := kyvernov1.Audit
log.V(4).Info("setting default value", "spec.validationFailureAction", audit)
patchByte, err := jsonutils.MarshalPatch("/spec/validationFailureAction", "add", audit)
patchByte, err := jsonutils.MarshalPatchOperation("/spec/validationFailureAction", "add", audit)
if err != nil {
log.Error(err, "failed to default value", "spec.validationFailureAction", audit)
return nil, ""
@ -88,7 +88,7 @@ func defaultFailurePolicy(spec *kyvernov1.Spec, log logr.Logger) ([]byte, string
if spec.FailurePolicy == nil {
failurePolicy := string(kyvernov1.Fail)
log.V(4).Info("setting default value", "spec.failurePolicy", failurePolicy)
patchByte, err := jsonutils.MarshalPatch("/spec/failurePolicy", "add", failurePolicy)
patchByte, err := jsonutils.MarshalPatchOperation("/spec/failurePolicy", "add", failurePolicy)
if err != nil {
log.Error(err, "failed to set default value", "spec.failurePolicy", failurePolicy)
return nil, ""
@ -155,13 +155,13 @@ func defaultPodControllerAnnotation(ann map[string]string, controllers string) (
if ann == nil {
ann = make(map[string]string)
ann[kyvernov1.PodControllersAnnotation] = controllers
patchByte, err := jsonutils.MarshalPatch("/metadata/annotations", "add", ann)
patchByte, err := jsonutils.MarshalPatchOperation("/metadata/annotations", "add", ann)
if err != nil {
return nil, err
}
return patchByte, nil
}
patchByte, err := jsonutils.MarshalPatch("/metadata/annotations/pod-policies.kyverno.io~1autogen-controllers", "add", controllers)
patchByte, err := jsonutils.MarshalPatchOperation("/metadata/annotations/pod-policies.kyverno.io~1autogen-controllers", "add", controllers)
if err != nil {
return nil, err
}

View file

@ -43,7 +43,7 @@ func GenerateAnnotationPatches(engineResponses []*response.EngineResponse, log l
if annotations == nil {
annotations = make(map[string]string)
}
var patchResponse jsonutils.Patch
var patchResponse jsonutils.PatchOperation
value := annotationFromEngineResponses(engineResponses, log)
if value == nil {
// no patches or error while processing patches
@ -52,30 +52,30 @@ func GenerateAnnotationPatches(engineResponses []*response.EngineResponse, log l
if _, ok := annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")]; ok {
// create update patch string
if _, ok := annotations["policies.kyverno.io/patches"]; ok {
patchResponse = jsonutils.NewPatch("/metadata/annotations/"+oldAnnotation, "remove", nil)
patchResponse = jsonutils.NewPatchOperation("/metadata/annotations/"+oldAnnotation, "remove", nil)
delete(annotations, "policies.kyverno.io/patches")
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
}
patchResponse = jsonutils.NewPatch("/metadata/annotations/"+policyAnnotation, "replace", string(value))
patchResponse = jsonutils.NewPatchOperation("/metadata/annotations/"+policyAnnotation, "replace", string(value))
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
} else {
// mutate rule has annotation patches
if len(annotations) > 0 {
if _, ok := annotations["policies.kyverno.io/patches"]; ok {
patchResponse = jsonutils.NewPatch("/metadata/annotations/"+oldAnnotation, "remove", nil)
patchResponse = jsonutils.NewPatchOperation("/metadata/annotations/"+oldAnnotation, "remove", nil)
delete(annotations, "policies.kyverno.io/patches")
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
}
patchResponse = jsonutils.NewPatch("/metadata/annotations/"+policyAnnotation, "add", string(value))
patchResponse = jsonutils.NewPatchOperation("/metadata/annotations/"+policyAnnotation, "add", string(value))
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
} else {
// insert 'policies.kyverno.patches' entry in annotation map
annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")] = string(value)
patchResponse = jsonutils.NewPatch("/metadata/annotations", "add", annotations)
patchResponse = jsonutils.NewPatchOperation("/metadata/annotations", "add", annotations)
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
}

View file

@ -6,21 +6,21 @@ import (
jsonpatch "github.com/evanphx/json-patch"
)
type Patch struct {
type PatchOperation struct {
Path string `json:"path"`
Op string `json:"op"`
Value interface{} `json:"value,omitempty"`
}
func NewPatch(path, op string, value interface{}) Patch {
return Patch{path, op, value}
func NewPatchOperation(path, op string, value interface{}) PatchOperation {
return PatchOperation{path, op, value}
}
func (p *Patch) Marshal() ([]byte, error) {
func (p *PatchOperation) Marshal() ([]byte, error) {
return json.Marshal(p)
}
func (p *Patch) ToPatchBytes() ([]byte, error) {
func (p *PatchOperation) ToPatchBytes() ([]byte, error) {
if patch, err := json.Marshal(p); err != nil {
return nil, err
} else {
@ -28,8 +28,8 @@ func (p *Patch) ToPatchBytes() ([]byte, error) {
}
}
func MarshalPatch(path, op string, value interface{}) ([]byte, error) {
p := NewPatch(path, op, value)
func MarshalPatchOperation(path, op string, value interface{}) ([]byte, error) {
p := NewPatchOperation(path, op, value)
return p.Marshal()
}
@ -38,8 +38,8 @@ func CheckPatch(patch []byte) error {
return err
}
func UnmarshalPatch(patch []byte) (*Patch, error) {
var p Patch
func UnmarshalPatchOperation(patch []byte) (*PatchOperation, error) {
var p PatchOperation
if err := json.Unmarshal(patch, &p); err != nil {
return nil, err
}

View file

@ -1,18 +1,37 @@
package json
import (
"strings"
)
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
// It accepts patch operations and patches (arrays of patch operations) and returns
// a single combined patch.
func JoinPatches(patches ...[]byte) []byte {
var result []byte
if len(patches) == 0 {
return result
return nil
}
result = append(result, []byte("[\n")...)
for index, patch := range patches {
result = append(result, patch...)
if index != len(patches)-1 {
result = append(result, []byte(",\n")...)
var patchOperations []string
for _, patch := range patches {
str := strings.TrimSpace(string(patch))
if len(str) == 0 {
continue
}
if strings.HasPrefix(str, "[") {
str = strings.TrimPrefix(str, "[")
str = strings.TrimSuffix(str, "]")
str = strings.TrimSpace(str)
}
patchOperations = append(patchOperations, str)
}
result = append(result, []byte("\n]")...)
return result
if len(patchOperations) == 0 {
return nil
}
result := "[" + strings.Join(patchOperations, ", ") + "]"
return []byte(result)
}

View file

@ -0,0 +1,49 @@
package json
import (
"testing"
jsonpatch "github.com/evanphx/json-patch"
"gotest.tools/assert"
)
func Test_JoinPatches(t *testing.T) {
patches := JoinPatches()
assert.Assert(t, patches == nil, "invalid patch %#v", string(patches))
patches = JoinPatches([]byte(""))
assert.Assert(t, patches == nil, "invalid patch %#v", string(patches))
patches = JoinPatches([]byte(""), []byte(""), []byte(""), []byte(""))
assert.Assert(t, patches == nil, "invalid patch %#v", string(patches))
p1 := `{ "op": "replace", "path": "/baz", "value": "boo" }`
p2 := `{ "op": "add", "path": "/hello", "value": ["world"] }`
p1p2 := `[
{ "op": "replace", "path": "/baz", "value": "boo" },
{ "op": "add", "path": "/hello", "value": ["world"] }
]`
patches = JoinPatches([]byte(p1), []byte(p2))
_, err := jsonpatch.DecodePatch(patches)
assert.NilError(t, err, "failed to decode patch %s", string(patches))
if !jsonpatch.Equal([]byte(p1p2), patches) {
assert.Assert(t, false, "patches are not equal")
}
p3 := `{ "op": "remove", "path": "/foo" }`
p1p2p3 := `[
{ "op": "replace", "path": "/baz", "value": "boo" },
{ "op": "add", "path": "/hello", "value": ["world"] },
{ "op": "remove", "path": "/foo" }
]`
patches = JoinPatches([]byte(p1p2), []byte(p3))
assert.NilError(t, err, "failed to join patches %s", string(patches))
_, err = jsonpatch.DecodePatch(patches)
assert.NilError(t, err, "failed to decode patch %s", string(patches))
if !jsonpatch.Equal([]byte(p1p2p3), patches) {
assert.Assert(t, false, "patches are not equal %+v %+v", p1p2p3, string(patches))
}
}

View file

@ -190,6 +190,7 @@ func (h *handlers) Validate(logger logr.Logger, request *admissionv1.AdmissionRe
go h.createUpdateRequests(logger, request, policyContext, generatePolicies, mutatePolicies, requestTime)
logger.V(4).Info("completed validating webhook")
return admissionutils.ResponseSuccess(true, "")
}
@ -234,7 +235,12 @@ func (h *handlers) Mutate(logger logr.Logger, request *admissionv1.AdmissionRequ
logger.Error(err, "image verification failed")
return admissionutils.ResponseFailure(false, err.Error())
}
return admissionutils.ResponseSuccessWithPatch(true, "", append(mutatePatches, imagePatches...))
patch := jsonutils.JoinPatches(mutatePatches, imagePatches)
admissionResponse := admissionutils.ResponseSuccessWithPatch(true, "", patch)
logger.V(4).Info("completed mutating webhook", "response", admissionResponse)
return admissionResponse
}
func (h *handlers) buildPolicyContext(request *admissionv1.AdmissionRequest, addRoles bool) (*engine.PolicyContext, error) {