1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 07:57:07 +00:00
kyverno/pkg/engine/mutate/patchesUtils.go
2020-10-07 11:12:31 -07:00

166 lines
4.3 KiB
Go

package mutate
import (
"fmt"
"path/filepath"
"reflect"
"strconv"
"strings"
"github.com/mattbaird/jsonpatch"
evanjsonpatch "github.com/evanphx/json-patch"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/pkg/api/kyverno/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
func generatePatches(src, dst []byte) ([][]byte, error) {
var patchesBytes [][]byte
pp, err := jsonpatch.CreatePatch(src, dst)
for _, p := range pp {
// TODO: handle remove nil value, i.e.,
// {remove /spec/securityContext <nil>}
// {remove /status/conditions/0/lastProbeTime <nil>}
pbytes, err := p.MarshalJSON()
if err != nil {
return patchesBytes, err
}
patchesBytes = append(patchesBytes, pbytes)
}
return patchesBytes, err
}
// preProcessJSONPatchesgo deals with the JsonPatch when reinvocation
// policy is set in webhook, to avoid generating duplicate values.
// This duplicate error only occurs on type array, if it's adding to a map
// the value will be added to the map if nil, otherwise it overwrites the old value
// return skip == true to skip the json patch application
func preProcessJSONPatches(mutation kyverno.Mutation, resource unstructured.Unstructured,
log logr.Logger) (skip bool, err error) {
var patches evanjsonpatch.Patch
log = log.WithName("preProcessJSONPatches")
if len(mutation.PatchesJSON6902) > 0 {
patches, err = decodePatch(mutation.PatchesJSON6902)
if err != nil {
return false, fmt.Errorf("failed to process JSON patches: %v", err)
}
}
for _, patch := range patches {
if patch.Kind() != "add" {
continue
}
path, err := patch.Path()
if err != nil {
return false, fmt.Errorf("failed to get path in JSON Patch: %v", err)
}
// check if the target is the list
if tail := filepath.Base(path); tail != "-" {
_, err := strconv.Atoi(tail)
if err != nil {
log.V(4).Info("JSON patch does not add to the list, skipping", "path", path)
continue
}
}
resourceObj, err := getObject(path, resource.UnstructuredContent())
if err != nil {
log.V(4).Info("failed to get object by the given path", "path", path, "error", err.Error())
continue
}
val, err := patch.ValueInterface()
if err != nil {
log.V(4).Info("failed to get value by the given path", "path", path, "error", err.Error())
continue
}
// if there's one patch exist in the resource, which indicates
// this is re-invoked JSON patches, skip application
if isSubsetObject(val, resourceObj) {
return true, nil
}
}
return false, nil
}
// - insert to the end of the list
// {"op": "add", "path": "/spec/containers/-", {"value": "{"name":"busyboxx","image":"busybox:latest"}"}
// - insert value to the certain element of the list
// {"op": "add", "path": "/spec/containers/1", {"value": "{"name":"busyboxx","image":"busybox:latest"}"}
func getObject(path string, resource map[string]interface{}) (interface{}, error) {
var strippedResource interface{}
strippedResource = resource
var ok bool
if strings.HasPrefix(path, "/") {
path = path[1:]
}
paths := strings.Split(path, "/")
for i, key := range paths {
switch strippedResource.(type) {
case map[string]interface{}:
strippedResource, ok = strippedResource.(map[string]interface{})[key]
if !ok {
return nil, fmt.Errorf("referenced value does not exist at %s", strings.Join(paths[:i+1], "/"))
}
case []interface{}:
var idx int
if key == "-" {
idx = len(strippedResource.([]interface{})) - 1
} else {
var err error
idx, err = strconv.Atoi(key)
if err != nil {
return nil, fmt.Errorf("cannot parse index in JSON Patch at %s: %v", strings.Join(paths[:i+1], "/"), err)
}
}
if len(strippedResource.([]interface{})) <= idx {
return nil, nil
}
strippedResource = strippedResource.([]interface{})[idx]
}
}
return strippedResource, nil
}
// isSubsetObject returns true if object is subset of resource
// the object/resource is the element inside the list, return false
// if the type is mismatched (not map)
func isSubsetObject(object, resource interface{}) bool {
objectMap, ok := object.(map[string]interface{})
if !ok {
return false
}
resourceMap, ok := resource.(map[string]interface{})
if !ok {
return false
}
for objKey, objVal := range objectMap {
rsrcVal, ok := resourceMap[objKey]
if !ok {
return false
}
if !reflect.DeepEqual(objVal, rsrcVal) {
return false
}
}
return true
}