Skip to content

Commit

Permalink
Merge branch 'master' into feature/reports-cli
Browse files Browse the repository at this point in the history
  • Loading branch information
yindia committed Sep 3, 2020
2 parents e15ed82 + 118b40c commit e63be74
Show file tree
Hide file tree
Showing 10 changed files with 473 additions and 45 deletions.
3 changes: 2 additions & 1 deletion go.mod
Expand Up @@ -9,7 +9,8 @@ require (
github.com/docker/docker v0.7.3-0.20190327010347-be7ac8be2ae0
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-metrics v0.0.1 // indirect
github.com/evanphx/json-patch v4.5.0+incompatible
github.com/evanphx/json-patch/v5 v5.0.0 // indirect
github.com/gardener/controller-manager-library v0.2.0
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
github.com/go-logr/logr v0.1.0
github.com/go-openapi/spec v0.19.5
Expand Down
46 changes: 46 additions & 0 deletions go.sum

Large diffs are not rendered by default.

27 changes: 0 additions & 27 deletions pkg/engine/mutate/generatePatches.go

This file was deleted.

31 changes: 29 additions & 2 deletions pkg/engine/mutate/mutation.go
Expand Up @@ -5,6 +5,7 @@ import (
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/context"
"github.com/nirmata/kyverno/pkg/engine/response"
"github.com/nirmata/kyverno/pkg/engine/utils"
"github.com/nirmata/kyverno/pkg/engine/variables"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
Expand Down Expand Up @@ -102,7 +103,20 @@ func newPatchesJSON6902Handler(ruleName string, mutate *kyverno.Mutation, patche
}
}

func (h patchesJSON6902Handler) Handle() (response.RuleResponse, unstructured.Unstructured) {
func (h patchesJSON6902Handler) Handle() (resp response.RuleResponse, patchedResource unstructured.Unstructured) {
resp.Name = h.ruleName
resp.Type = utils.Mutation.String()

skip, err := preProcessJSONPatches(*h.mutation, h.patchedResource, h.logger)
if err != nil {
h.logger.Error(err, "failed to preProcessJSONPatches")
}

if skip {
resp.Success = true
return resp, h.patchedResource
}

return ProcessPatchJSON6902(h.ruleName, *h.mutation, h.patchedResource, h.logger)
}

Expand Down Expand Up @@ -141,7 +155,20 @@ func newpatchesHandler(ruleName string, mutate *kyverno.Mutation, patchedResourc
}
}

func (h patchesHandler) Handle() (response.RuleResponse, unstructured.Unstructured) {
func (h patchesHandler) Handle() (resp response.RuleResponse, patchedResource unstructured.Unstructured) {
resp.Name = h.ruleName
resp.Type = utils.Mutation.String()

skip, err := preProcessJSONPatches(*h.mutation, h.patchedResource, h.logger)
if err != nil {
h.logger.Error(err, "failed to preProcessJSONPatches")
}

if skip {
resp.Success = true
return resp, h.patchedResource
}

return ProcessPatches(h.logger, h.ruleName, *h.mutation, h.patchedResource)
}

Expand Down
21 changes: 21 additions & 0 deletions pkg/engine/mutate/patchJson6902.go
Expand Up @@ -4,8 +4,10 @@ import (
"bytes"
"encoding/json"
"fmt"
"strings"
"time"

jsonpatch "github.com/evanphx/json-patch"
"github.com/go-logr/logr"
kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/engine/response"
Expand Down Expand Up @@ -101,3 +103,22 @@ func patchJSON6902(base, patches string) ([]byte, error) {

return baseObj.Bytes(), err
}

func decodePatch(patch string) (jsonpatch.Patch, error) {
// If the patch doesn't look like a JSON6902 patch, we
// try to parse it to json.
if !strings.HasPrefix(patch, "[") {
p, err := yaml.YAMLToJSON([]byte(patch))
if err != nil {
return nil, err
}
patch = string(p)
}

decodedPatch, err := jsonpatch.DecodePatch([]byte(patch))
if err != nil {
return nil, err
}

return decodedPatch, nil
}
167 changes: 167 additions & 0 deletions pkg/engine/mutate/patchesUtils.go
@@ -0,0 +1,167 @@
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/nirmata/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)
fmt.Printf("generated patch %s\n", p)
}

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
}

0 comments on commit e63be74

Please sign in to comment.