1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-13 19:28:55 +00:00

127: Implemented usage of result package in validation and mutation functions.

This commit is contained in:
Denis Belyshev 2019-06-05 13:43:07 +03:00
parent 55fe28d5bf
commit e571f730b2
19 changed files with 625 additions and 343 deletions

3
.gitignore vendored
View file

@ -1,5 +1,6 @@
certs
Gopkg.lock
.vscode
kyverno
gh-pages/public
_output
_output

View file

@ -46,7 +46,7 @@ func Test_ResourceDescription_EmptyNameAndSelector(t *testing.T) {
Kinds: []string{"Deployment"},
}
err := resourceDescription.Validate()
assert.Assert(t, err != nil)
assert.NilError(t, err)
}
func Test_Patch_EmptyPath(t *testing.T) {

View file

@ -8,7 +8,7 @@ import (
types "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
lister "github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
event "github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/sharedinformer"
violation "github.com/nirmata/kyverno/pkg/violation"
"k8s.io/apimachinery/pkg/api/errors"

View file

@ -11,6 +11,7 @@ import (
// The each function returns the changes that need to be applied on the resource
// the caller is responsible to apply the changes to the resource
// ProcessExisting checks for mutation and validation violations of existing resources
func ProcessExisting(policy types.Policy, rawResource []byte) ([]violation.Info, []event.Info, error) {
var violations []violation.Info
var events []event.Info

View file

@ -1,52 +1,59 @@
package engine
import (
"github.com/golang/glog"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// Mutate performs mutation. Overlay first and then mutation patches
// TODO: return events and violations
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, []byte) {
var policyPatches []PatchBytes
var processedPatches []PatchBytes
var err error
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, result.Result) {
var allPatches []PatchBytes
patchedDocument := rawResource
policyResult := result.NewPolicyApplicationResult(policy.Name)
for _, rule := range policy.Spec.Rules {
if rule.Mutation == nil {
continue
}
ruleApplicationResult := result.NewRuleApplicationResult(rule.Name)
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
if !ok {
glog.Infof("Rule \"%s\" is not applicable to resource\n", rule.Name)
ruleApplicationResult.AddMessagef("Rule %s is not applicable to resource\n", rule.Name)
policyResult = result.Append(policyResult, &ruleApplicationResult)
continue
}
// Process Overlay
if rule.Mutation.Overlay != nil {
overlayPatches, err := ProcessOverlay(policy, rawResource, gvk)
if err != nil {
glog.Warningf("Overlay application has failed for rule %s in policy %s, err: %v\n", rule.Name, policy.ObjectMeta.Name, err)
overlayPatches, ruleResult := ProcessOverlay(rule.Mutation.Overlay, rawResource, gvk)
if result.Success != ruleResult.GetReason() {
ruleApplicationResult.MergeWith(&ruleResult)
ruleApplicationResult.AddMessagef("Overlay application has failed for rule %s in policy %s\n", rule.Name, policy.ObjectMeta.Name)
} else {
policyPatches = append(policyPatches, overlayPatches...)
ruleApplicationResult.AddMessagef("Success")
allPatches = append(allPatches, overlayPatches...)
}
}
// Process Patches
if rule.Mutation.Patches != nil {
processedPatches, patchedDocument, err = ProcessPatches(rule.Mutation.Patches, patchedDocument)
if err != nil {
glog.Warningf("Patches application has failed for rule %s in policy %s, err: %v\n", rule.Name, policy.ObjectMeta.Name, err)
rulePatches, ruleResult := ProcessPatches(rule.Mutation.Patches, patchedDocument)
if result.Success != ruleResult.GetReason() {
ruleApplicationResult.MergeWith(&ruleResult)
ruleApplicationResult.AddMessagef("Patches application has failed for rule %s in policy %s\n", rule.Name, policy.ObjectMeta.Name)
} else {
policyPatches = append(policyPatches, processedPatches...)
ruleApplicationResult.AddMessagef("Success")
allPatches = append(allPatches, rulePatches...)
}
}
}
return policyPatches, patchedDocument
return allPatches, policyResult
}

View file

@ -7,57 +7,42 @@ import (
"strconv"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ProcessOverlay handles validating admission request
// Checks the target resourse for rules defined in the policy
func ProcessOverlay(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, error) {
func ProcessOverlay(overlay interface{}, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, result.RuleApplicationResult) {
var resource interface{}
var appliedPatches []PatchBytes
json.Unmarshal(rawResource, &resource)
var appliedPatches []PatchBytes
for _, rule := range policy.Spec.Rules {
if rule.Mutation == nil || rule.Mutation.Overlay == nil {
continue
}
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
if !ok {
glog.Infof("Rule \"%s\" is not applicable to resource\n", rule.Name)
continue
}
overlay := *rule.Mutation.Overlay
patch, err := applyOverlay(resource, overlay, "/")
if err != nil {
return nil, fmt.Errorf("Overlay application failed: %v", err.Error())
}
overlayApplicationResult := result.NewRuleApplicationResult("")
if overlay == nil {
return nil, overlayApplicationResult
}
patch := applyOverlay(resource, overlay, "/", &overlayApplicationResult)
if overlayApplicationResult.GetReason() == result.Success {
appliedPatches = append(appliedPatches, patch...)
}
return appliedPatches, nil
return appliedPatches, overlayApplicationResult
}
// goes down through overlay and resource trees and applies overlay
func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, error) {
func applyOverlay(resource, overlay interface{}, path string, res *result.RuleApplicationResult) []PatchBytes {
var appliedPatches []PatchBytes
// resource item exists but has different type - replace
// all subtree within this path by overlay
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
patch, err := replaceSubtree(overlay, path)
if err != nil {
return nil, err
patch := replaceSubtree(overlay, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
return appliedPatches, nil
return appliedPatches
}
switch typedOverlay := overlay.(type) {
@ -72,17 +57,15 @@ func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, err
resourcePart, ok := typedResource[key]
if ok {
patches, err := applyOverlay(resourcePart, value, currentPath)
if err != nil {
return nil, err
patches := applyOverlay(resourcePart, value, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
appliedPatches = append(appliedPatches, patches...)
} else {
patch, err := insertSubtree(value, currentPath)
if err != nil {
return nil, err
patch := insertSubtree(value, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
@ -90,44 +73,38 @@ func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, err
}
case []interface{}:
typedResource := resource.([]interface{})
patches, err := applyOverlayToArray(typedResource, typedOverlay, path)
if err != nil {
return nil, err
patches := applyOverlayToArray(typedResource, typedOverlay, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
appliedPatches = append(appliedPatches, patches...)
case string, float64, int64, bool:
patch, err := replaceSubtree(overlay, path)
if err != nil {
return nil, err
patch := replaceSubtree(overlay, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
default:
return nil, fmt.Errorf("Overlay has unsupported type: %T", overlay)
res.FailWithMessagef("Overlay has unsupported type: %T", overlay)
return nil
}
return appliedPatches, nil
return appliedPatches
}
// for each overlay and resource array elements and applies overlay
func applyOverlayToArray(resource, overlay []interface{}, path string) ([]PatchBytes, error) {
func applyOverlayToArray(resource, overlay []interface{}, path string, res *result.RuleApplicationResult) []PatchBytes {
var appliedPatches []PatchBytes
if len(overlay) == 0 {
return nil, fmt.Errorf("overlay does not support empty arrays")
res.FailWithMessagef("Empty array detected in the overlay")
return nil
}
if len(resource) == 0 {
patches, err := fillEmptyArray(overlay, path)
if err != nil {
return nil, err
}
return patches, nil
return fillEmptyArray(overlay, path, res)
}
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
return nil, fmt.Errorf("overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
res.FailWithMessagef("overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
return nil
}
switch overlay[0].(type) {
@ -141,53 +118,49 @@ func applyOverlayToArray(resource, overlay []interface{}, path string) ([]PatchB
currentPath := path + strconv.Itoa(i) + "/"
if !skipArrayObject(typedResource, anchors) {
patches, err := applyOverlay(resourceElement, overlayElement, currentPath)
if err != nil {
return nil, err
patches := applyOverlay(resourceElement, overlayElement, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
appliedPatches = append(appliedPatches, patches...)
}
}
} else if hasNestedAnchors(overlayElement) {
for i, resourceElement := range resource {
currentPath := path + strconv.Itoa(i) + "/"
patches, err := applyOverlay(resourceElement, overlayElement, currentPath)
if err != nil {
return nil, err
patches := applyOverlay(resourceElement, overlayElement, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
appliedPatches = append(appliedPatches, patches...)
}
} else {
currentPath := path + "0/"
patch, err := insertSubtree(overlayElement, currentPath)
if err != nil {
return nil, err
patch := insertSubtree(overlayElement, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
}
}
default:
path += "0/"
for _, value := range overlay {
patch, err := insertSubtree(value, path)
if err != nil {
return nil, err
patch := insertSubtree(value, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
}
}
return appliedPatches, nil
return appliedPatches
}
// In case of empty resource array
// append all non-anchor items to front
func fillEmptyArray(overlay []interface{}, path string) ([]PatchBytes, error) {
func fillEmptyArray(overlay []interface{}, path string, res *result.RuleApplicationResult) []PatchBytes {
var appliedPatches []PatchBytes
if len(overlay) == 0 {
return nil, fmt.Errorf("overlay does not support empty arrays")
res.FailWithMessagef("Empty array detected in the overlay")
return nil
}
path += "0/"
@ -199,55 +172,33 @@ func fillEmptyArray(overlay []interface{}, path string) ([]PatchBytes, error) {
anchors := GetAnchorsFromMap(typedOverlay)
if len(anchors) == 0 {
patch, err := insertSubtree(overlayElement, path)
if err != nil {
return nil, err
patch := insertSubtree(overlayElement, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
}
}
default:
for _, overlayElement := range overlay {
patch, err := insertSubtree(overlayElement, path)
if err != nil {
return nil, err
patch := insertSubtree(overlayElement, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
}
}
return appliedPatches, nil
return appliedPatches
}
// Checks if array object matches anchors. If not - skip - return true
func skipArrayObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := object[key]
if !ok {
return true
}
if !ValidateValueWithPattern(value, pattern) {
return true
}
}
return false
func insertSubtree(overlay interface{}, path string, res *result.RuleApplicationResult) []byte {
return processSubtree(overlay, path, "add", res)
}
func insertSubtree(overlay interface{}, path string) ([]byte, error) {
return processSubtree(overlay, path, "add")
func replaceSubtree(overlay interface{}, path string, res *result.RuleApplicationResult) []byte {
return processSubtree(overlay, path, "replace", res)
}
func replaceSubtree(overlay interface{}, path string) ([]byte, error) {
return processSubtree(overlay, path, "replace")
}
func processSubtree(overlay interface{}, path string, op string) ([]byte, error) {
func processSubtree(overlay interface{}, path string, op string, res *result.RuleApplicationResult) PatchBytes {
if len(path) > 1 && path[len(path)-1] == '/' {
path = path[:len(path)-1]
}
@ -262,10 +213,11 @@ func processSubtree(overlay interface{}, path string, op string) ([]byte, error)
// check the patch
_, err := jsonpatch.DecodePatch([]byte("[" + patchStr + "]"))
if err != nil {
return nil, err
res.FailWithMessagef("Failed to make '%s' patch from an overlay for path %s", op, path)
return nil
}
return []byte(patchStr), nil
return PatchBytes(patchStr)
}
// TODO: Overlay is already in JSON, remove this code

View file

@ -2,12 +2,21 @@ package engine
import (
"encoding/json"
"github.com/nirmata/kyverno/pkg/result"
"reflect"
"testing"
jsonpatch "github.com/evanphx/json-patch"
"gotest.tools/assert"
)
func compareJsonAsMap(t *testing.T, expected, actual []byte) {
var expectedMap, actualMap map[string]interface{}
assert.NilError(t, json.Unmarshal(expected, &expectedMap))
assert.NilError(t, json.Unmarshal(actual, &actualMap))
assert.Assert(t, reflect.DeepEqual(expectedMap, actualMap))
}
func TestApplyOverlay_NestedListWithAnchor(t *testing.T) {
resourceRaw := []byte(`
{
@ -57,8 +66,9 @@ func TestApplyOverlay_NestedListWithAnchor(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
patches, err := applyOverlay(resource, overlay, "/")
assert.NilError(t, err)
res := result.NewRuleApplicationResult("")
patches := applyOverlay(resource, overlay, "/", &res)
assert.NilError(t, res.ToError())
assert.Assert(t, patches != nil)
patch := JoinPatches(patches)
@ -71,7 +81,7 @@ func TestApplyOverlay_NestedListWithAnchor(t *testing.T) {
assert.Assert(t, patched != nil)
expectedResult := []byte(`{"apiVersion":"v1","kind":"Endpoints","metadata":{"name":"test-endpoint","labels":{"label":"test"}},"subsets":[{"addresses":[{"ip":"192.168.10.171"}],"ports":[{"name":"secure-connection","port":444.000000,"protocol":"UDP"}]}]}`)
assert.Equal(t, string(expectedResult), string(patched))
compareJsonAsMap(t, expectedResult, patched)
}
func TestApplyOverlay_InsertIntoArray(t *testing.T) {
@ -130,8 +140,9 @@ func TestApplyOverlay_InsertIntoArray(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
patches, err := applyOverlay(resource, overlay, "/")
assert.NilError(t, err)
res := result.NewRuleApplicationResult("")
patches := applyOverlay(resource, overlay, "/", &res)
assert.NilError(t, res.ToError())
assert.Assert(t, patches != nil)
patch := JoinPatches(patches)
@ -145,7 +156,7 @@ func TestApplyOverlay_InsertIntoArray(t *testing.T) {
assert.Assert(t, patched != nil)
expectedResult := []byte(`{"apiVersion":"v1","kind":"Endpoints","metadata":{"name":"test-endpoint","labels":{"label":"test"}},"subsets":[{"addresses":[{"ip":"192.168.10.172"},{"ip":"192.168.10.173"}],"ports":[{"name":"insecure-connection","port":80.000000,"protocol":"UDP"}]},{"addresses":[{"ip":"192.168.10.171"}],"ports":[{"name":"secure-connection","port":443,"protocol":"TCP"}]}]}`)
assert.Equal(t, string(expectedResult), string(patched))
compareJsonAsMap(t, expectedResult, patched)
}
func TestApplyOverlay_TestInsertToArray(t *testing.T) {
@ -208,8 +219,9 @@ func TestApplyOverlay_TestInsertToArray(t *testing.T) {
json.Unmarshal(resourceRaw, &resource)
json.Unmarshal(overlayRaw, &overlay)
patches, err := applyOverlay(resource, overlay, "/")
assert.NilError(t, err)
res := result.NewRuleApplicationResult("")
patches := applyOverlay(resource, overlay, "/", &res)
assert.NilError(t, res.ToError())
assert.Assert(t, patches != nil)
patch := JoinPatches(patches)

View file

@ -2,28 +2,35 @@ package engine
import (
"encoding/json"
"errors"
"fmt"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
)
// PatchBytes stands for []byte
type PatchBytes []byte
// ProcessPatches Returns array from separate patches that can be applied to the document
// Returns error ONLY in case when creation of resource should be denied.
func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, []byte, error) {
if len(resource) == 0 {
return nil, nil, errors.New("Source document for patching is empty")
func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes, result.RuleApplicationResult) {
res := result.RuleApplicationResult{
Reason: result.Success,
}
var appliedPatches []PatchBytes
if len(resource) == 0 {
res.AddMessagef("Source document for patching is empty")
res.Reason = result.Failed
return nil, res
}
var allPatches []PatchBytes
patchedDocument := resource
for i, patch := range patches {
patchRaw, err := json.Marshal(patch)
if err != nil {
return nil, nil, err
}
patchedDocument, err = applyPatch(patchedDocument, patchRaw)
@ -32,13 +39,14 @@ func ProcessPatches(patches []kubepolicy.Patch, resource []byte) ([]PatchBytes,
if patch.Operation == "remove" {
continue
}
glog.Warningf("Patch failed: patch number = %d, patch Operation = %s, err: %v", i, patch.Operation, err)
message := fmt.Sprintf("Patch failed: patch number = %d, patch Operation = %s, err: %v", i, patch.Operation, err)
res.Messages = append(res.Messages, message)
continue
}
appliedPatches = append(appliedPatches, patchRaw)
allPatches = append(allPatches, patchRaw)
}
return appliedPatches, patchedDocument, nil
return allPatches, res
}
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
@ -59,12 +67,16 @@ func JoinPatches(patches []PatchBytes) PatchBytes {
return result
}
// ApplyPatch applies patch for resource, returns patched resource.
// applyPatch applies patch for resource, returns patched resource.
func applyPatch(resource []byte, patchRaw []byte) ([]byte, error) {
patchRaw = append([]byte{'['}, patchRaw...) // push [ forward
patchRaw = append(patchRaw, ']') // push ] back
patchesList := []PatchBytes{patchRaw}
return ApplyPatches(resource, patchesList)
}
patch, err := jsonpatch.DecodePatch(patchRaw)
// ApplyPatches patches given resource with given patches and returns patched document
func ApplyPatches(resource []byte, patches []PatchBytes) ([]byte, error) {
joinedPatches := JoinPatches(patches)
patch, err := jsonpatch.DecodePatch(joinedPatches)
if err != nil {
return nil, err
}

View file

@ -35,8 +35,8 @@ const endpointsDocument string = `{
func TestProcessPatches_EmptyPatches(t *testing.T) {
var empty []types.Patch
patches, _, err := ProcessPatches(empty, []byte(endpointsDocument))
assert.NilError(t, err)
patches, res := ProcessPatches(empty, []byte(endpointsDocument))
assert.NilError(t, res.ToError())
assert.Assert(t, len(patches) == 0)
}
@ -51,14 +51,14 @@ func makeAddIsMutatedLabelPatch() types.Patch {
func TestProcessPatches_EmptyDocument(t *testing.T) {
var patches []types.Patch
patches = append(patches, makeAddIsMutatedLabelPatch())
patchesBytes, _, err := ProcessPatches(patches, nil)
assert.Assert(t, err != nil)
patchesBytes, res := ProcessPatches(patches, nil)
assert.Assert(t, res.ToError() != nil)
assert.Assert(t, len(patchesBytes) == 0)
}
func TestProcessPatches_AllEmpty(t *testing.T) {
patchesBytes, _, err := ProcessPatches(nil, nil)
assert.Assert(t, err != nil)
patchesBytes, res := ProcessPatches(nil, nil)
assert.Assert(t, res.ToError() != nil)
assert.Assert(t, len(patchesBytes) == 0)
}
@ -66,16 +66,16 @@ func TestProcessPatches_AddPathDoesntExist(t *testing.T) {
patch := makeAddIsMutatedLabelPatch()
patch.Path = "/metadata/additional/is-mutated"
patches := []types.Patch{patch}
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, err)
patchesBytes, res := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, res.ToError())
assert.Assert(t, len(patchesBytes) == 0)
}
func TestProcessPatches_RemovePathDoesntExist(t *testing.T) {
patch := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
patches := []types.Patch{patch}
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, err)
patchesBytes, res := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, res.ToError())
assert.Assert(t, len(patchesBytes) == 0)
}
@ -83,8 +83,8 @@ func TestProcessPatches_AddAndRemovePathsDontExist_EmptyResult(t *testing.T) {
patch1 := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
patch2 := types.Patch{Path: "/spec/labels/label3", Operation: "add", Value: "label3Value"}
patches := []types.Patch{patch1, patch2}
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, err)
patchesBytes, res := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, res.ToError())
assert.Assert(t, len(patchesBytes) == 0)
}
@ -93,8 +93,8 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul
patch2 := types.Patch{Path: "/spec/labels/label2", Operation: "remove", Value: "label2Value"}
patch3 := types.Patch{Path: "/metadata/labels/label3", Operation: "add", Value: "label3Value"}
patches := []types.Patch{patch1, patch2, patch3}
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, err)
patchesBytes, res := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, res.ToError())
assert.Assert(t, len(patchesBytes) == 1)
assertEqStringAndData(t, `{"path":"/metadata/labels/label3","op":"add","value":"label3Value"}`, patchesBytes[0])
}
@ -102,8 +102,8 @@ func TestProcessPatches_AddAndRemovePathsDontExist_ContinueOnError_NotEmptyResul
func TestProcessPatches_RemovePathDoesntExist_EmptyResult(t *testing.T) {
patch := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
patches := []types.Patch{patch}
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, err)
patchesBytes, res := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, res.ToError())
assert.Assert(t, len(patchesBytes) == 0)
}
@ -111,8 +111,8 @@ func TestProcessPatches_RemovePathDoesntExist_NotEmptyResult(t *testing.T) {
patch1 := types.Patch{Path: "/metadata/labels/is-mutated", Operation: "remove"}
patch2 := types.Patch{Path: "/metadata/labels/label2", Operation: "add", Value: "label2Value"}
patches := []types.Patch{patch1, patch2}
patchesBytes, _, err := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, err)
patchesBytes, res := ProcessPatches(patches, []byte(endpointsDocument))
assert.NilError(t, res.ToError())
assert.Assert(t, len(patchesBytes) == 1)
assertEqStringAndData(t, `{"path":"/metadata/labels/label2","op":"add","value":"label2Value"}`, patchesBytes[0])
}

View file

@ -1,7 +1,6 @@
package engine
import (
"fmt"
"math"
"regexp"
"strconv"
@ -166,6 +165,7 @@ func validateString(value interface{}, pattern string, operator Operator) bool {
}
func validateNumberWithStr(value interface{}, patternNumber, patternStr string, operator Operator) bool {
// pattern has suffix
if "" != patternStr {
typedValue, ok := value.(string)
if !ok {
@ -281,14 +281,6 @@ func getNumberAndStringPartsFromPattern(pattern string) (number, str string) {
return match[1], match[3]
}
func checkForWildcard(value, pattern string) error {
if !wildcard.Match(pattern, value) {
return fmt.Errorf("wildcard check has failed. Pattern: \"%s\". Value: \"%s\"", pattern, value)
}
return nil
}
func parseNumber(number string) (interface{}, error) {
var err error

View file

@ -125,3 +125,29 @@ func findKind(kinds []string, kindGVK string) bool {
}
return false
}
func wrappedWithParentheses(str string) bool {
if len(str) < 2 {
return false
}
return (str[0] == '(' && str[len(str)-1] == ')')
}
// Checks if array object matches anchors. If not - skip - return true
func skipArrayObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := object[key]
if !ok {
return true
}
if !ValidateValueWithPattern(value, pattern) {
return true
}
}
return false
}

View file

@ -2,166 +2,148 @@ package engine
import (
"encoding/json"
"fmt"
"strconv"
"github.com/golang/glog"
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// TODO: Refactor using State pattern
// TODO: Return Events and pass all checks to get all validation errors (not )
// Validate handles validating admission request
// Checks the target resourse for rules defined in the policy
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) error {
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) result.Result {
var resource interface{}
json.Unmarshal(rawResource, &resource)
policyResult := result.NewPolicyApplicationResult(policy.Name)
for _, rule := range policy.Spec.Rules {
if rule.Validation == nil {
continue
}
ruleApplicationResult := result.NewRuleApplicationResult(rule.Name)
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
if !ok {
glog.Infof("Rule \"%s\" is not applicable to resource\n", rule.Name)
ruleApplicationResult.AddMessagef("Rule %s is not applicable to resource\n", rule.Name)
policyResult = result.Append(policyResult, &ruleApplicationResult)
continue
}
if err := validateMap(resource, rule.Validation.Pattern); err != nil {
message := *rule.Validation.Message
if len(message) == 0 {
message = fmt.Sprintf("%v", err)
} else {
message = fmt.Sprintf("%s, %s", message, err.Error())
}
return fmt.Errorf("%s: %s", *rule.Validation.Message, err.Error())
validationResult := validateResourceWithPattern(resource, rule.Validation.Pattern)
if result.Success != validationResult.Reason {
ruleApplicationResult.MergeWith(&validationResult)
ruleApplicationResult.AddMessagef(*rule.Validation.Message)
} else {
ruleApplicationResult.AddMessagef("Success")
}
policyResult = result.Append(policyResult, &ruleApplicationResult)
}
return nil
return policyResult
}
func validateMap(resourcePart, patternPart interface{}) error {
pattern, ok := patternPart.(map[string]interface{})
if !ok {
return fmt.Errorf("expected map, found %T", patternPart)
}
func validateResourceWithPattern(resource, pattern interface{}) result.RuleApplicationResult {
return validateResourceElement(resource, pattern, "/")
}
resource, ok := resourcePart.(map[string]interface{})
if !ok {
return fmt.Errorf("expected map, found %T", resourcePart)
}
func validateResourceElement(value, pattern interface{}, path string) result.RuleApplicationResult {
res := result.NewRuleApplicationResult("")
// TODO: Move similar message templates to message package
for key, value := range pattern {
switch typedPattern := pattern.(type) {
case map[string]interface{}:
typedValue, ok := value.(map[string]interface{})
if !ok {
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", pattern, value, path)
return res
}
return validateMap(typedValue, typedPattern, path)
case []interface{}:
typedValue, ok := value.([]interface{})
if !ok {
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", pattern, value, path)
return res
}
return validateArray(typedValue, typedPattern, path)
case string, float64, int, int64, bool:
if !ValidateValueWithPattern(value, pattern) {
res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", value, pattern, path)
}
return res
default:
res.FailWithMessagef("Pattern contains unknown type %T. Path: %s", pattern, path)
return res
}
}
func validateMap(valueMap, patternMap map[string]interface{}, path string) result.RuleApplicationResult {
res := result.NewRuleApplicationResult("")
for key, pattern := range patternMap {
if wrappedWithParentheses(key) {
key = key[1 : len(key)-1]
}
if value == "*" && resource[key] != nil {
if pattern == "*" && valueMap[key] != nil {
continue
} else if value == "*" && resource[key] == nil {
return fmt.Errorf("validating error: field %s must be present", key)
} else if pattern == "*" && valueMap[key] == nil {
res.FailWithMessagef("Field %s is not present", key)
} else {
if err := validateMapElement(resource[key], value); err != nil {
return err
elementResult := validateResourceElement(valueMap[key], pattern, path+key+"/")
if result.Failed == elementResult.Reason {
res.Reason = elementResult.Reason
res.Messages = append(res.Messages, elementResult.Messages...)
}
}
}
return nil
return res
}
func validateArray(resourcePart, patternPart interface{}) error {
patternArray, ok := patternPart.([]interface{})
if !ok {
return fmt.Errorf("expected array, found %T", patternPart)
}
func validateArray(resourceArray, patternArray []interface{}, path string) result.RuleApplicationResult {
res := result.NewRuleApplicationResult("")
resourceArray, ok := resourcePart.([]interface{})
if !ok {
return fmt.Errorf("expected array, found %T", resourcePart)
if 0 == len(patternArray) {
return res
}
switch pattern := patternArray[0].(type) {
case map[string]interface{}:
anchors := GetAnchorsFromMap(pattern)
for _, value := range resourceArray {
for i, value := range resourceArray {
currentPath := path + strconv.Itoa(i) + "/"
resource, ok := value.(map[string]interface{})
if !ok {
return fmt.Errorf("expected array, found %T", resourcePart)
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", pattern, value, currentPath)
return res
}
if skipValidatingObject(resource, anchors) {
if skipArrayObject(resource, anchors) {
continue
}
if err := validateMap(resource, pattern); err != nil {
return err
mapValidationResult := validateMap(resource, pattern, currentPath)
if result.Failed == mapValidationResult.Reason {
res.Reason = mapValidationResult.Reason
res.Messages = append(res.Messages, mapValidationResult.Messages...)
}
}
default:
case string, float64, int, int64, bool:
for _, value := range resourceArray {
if !ValidateValueWithPattern(value, patternArray[0]) {
return fmt.Errorf("Failed validate %v with %v", value, patternArray[0])
if !ValidateValueWithPattern(value, pattern) {
res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", value, pattern, path)
}
}
}
return nil
}
func validateMapElement(resourcePart, patternPart interface{}) error {
switch pattern := patternPart.(type) {
case map[string]interface{}:
dictionary, ok := resourcePart.(map[string]interface{})
if !ok {
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
}
return validateMap(dictionary, pattern)
case []interface{}:
array, ok := resourcePart.([]interface{})
if !ok {
return fmt.Errorf("expected %T, found %T", patternPart, resourcePart)
}
return validateArray(array, pattern)
case string, float64, int, int64, bool, nil:
if !ValidateValueWithPattern(resourcePart, patternPart) {
return fmt.Errorf("Failed validate %v with %v", resourcePart, patternPart)
}
default:
return fmt.Errorf("validating error: unknown type in map: %T", patternPart)
res.FailWithMessagef("Array element pattern of unknown type %T. Path: %s", pattern, path)
}
return nil
}
func skipValidatingObject(object, anchors map[string]interface{}) bool {
for key, pattern := range anchors {
key = key[1 : len(key)-1]
value, ok := object[key]
if !ok {
return true
}
if !ValidateValueWithPattern(value, pattern) {
return true
}
}
return false
}
func wrappedWithParentheses(str string) bool {
if len(str) < 2 {
return false
}
return (str[0] == '(' && str[len(str)-1] == ')')
return res
}

View file

@ -44,46 +44,46 @@ func TestWrappedWithParentheses_Empty(t *testing.T) {
assert.Assert(t, !wrappedWithParentheses(str))
}
func TestCheckForWildcard_AsteriskTest(t *testing.T) {
func TestValidateString_AsteriskTest(t *testing.T) {
pattern := "*"
value := "anything"
empty := ""
assert.Assert(t, checkForWildcard(value, pattern))
assert.Assert(t, checkForWildcard(empty, pattern))
assert.Assert(t, validateString(value, pattern, Equal))
assert.Assert(t, validateString(empty, pattern, Equal))
}
func TestCheckForWildcard_LeftAsteriskTest(t *testing.T) {
func TestValidateString_LeftAsteriskTest(t *testing.T) {
pattern := "*right"
value := "leftright"
right := "right"
assert.Assert(t, checkForWildcard(value, pattern))
assert.Assert(t, checkForWildcard(right, pattern))
assert.Assert(t, validateString(value, pattern, Equal))
assert.Assert(t, validateString(right, pattern, Equal))
value = "leftmiddle"
middle := "middle"
assert.Assert(t, checkForWildcard(value, pattern) != nil)
assert.Assert(t, checkForWildcard(middle, pattern) != nil)
assert.Assert(t, !validateString(value, pattern, Equal))
assert.Assert(t, !validateString(middle, pattern, Equal))
}
func TestCheckForWildcard_MiddleAsteriskTest(t *testing.T) {
func TestValidateString_MiddleAsteriskTest(t *testing.T) {
pattern := "ab*ba"
value := "abbeba"
assert.NilError(t, checkForWildcard(value, pattern))
assert.Assert(t, validateString(value, pattern, Equal))
value = "abbca"
assert.Assert(t, checkForWildcard(value, pattern) != nil)
assert.Assert(t, !validateString(value, pattern, Equal))
}
func TestCheckForWildcard_QuestionMark(t *testing.T) {
func TestValidateString_QuestionMark(t *testing.T) {
pattern := "ab?ba"
value := "abbba"
assert.NilError(t, checkForWildcard(value, pattern))
assert.Assert(t, validateString(value, pattern, Equal))
value = "abbbba"
assert.Assert(t, checkForWildcard(value, pattern) != nil)
assert.Assert(t, !validateString(value, pattern, Equal))
}
func TestSkipArrayObject_OneAnchor(t *testing.T) {
@ -142,7 +142,7 @@ func TestSkipArrayObject_OneNumberAnchorPass(t *testing.T) {
func TestSkipArrayObject_TwoAnchorsPass(t *testing.T) {
rawAnchors := []byte(`{
"(name)":"nirmata-*",
"(namespace)":"kube-?olicy"
"(namespace)":"kyv?rno"
}`)
rawResource := []byte(`{
"name":"nirmata-resource",
@ -320,11 +320,12 @@ func TestValidateMap(t *testing.T) {
}
}`)
var pattern, resource interface{}
var pattern, resource map[string]interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.NilError(t, validateMap(resource, pattern))
res := validateMap(resource, pattern, "/")
assert.NilError(t, res.ToError())
}
func TestValidateMap_AsteriskForInt(t *testing.T) {
@ -414,11 +415,12 @@ func TestValidateMap_AsteriskForInt(t *testing.T) {
}
`)
var pattern, resource interface{}
var pattern, resource map[string]interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.NilError(t, validateMap(resource, pattern))
res := validateMap(resource, pattern, "/")
assert.NilError(t, res.ToError())
}
func TestValidateMap_AsteriskForMap(t *testing.T) {
@ -505,11 +507,12 @@ func TestValidateMap_AsteriskForMap(t *testing.T) {
}
}`)
var pattern, resource interface{}
var pattern, resource map[string]interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.NilError(t, validateMap(resource, pattern))
res := validateMap(resource, pattern, "/")
assert.NilError(t, res.ToError())
}
func TestValidateMap_AsteriskForArray(t *testing.T) {
@ -591,11 +594,12 @@ func TestValidateMap_AsteriskForArray(t *testing.T) {
}
}`)
var pattern, resource interface{}
var pattern, resource map[string]interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.NilError(t, validateMap(resource, pattern))
res := validateMap(resource, pattern, "/")
assert.NilError(t, res.ToError())
}
func TestValidateMap_AsteriskFieldIsMissing(t *testing.T) {
@ -680,11 +684,12 @@ func TestValidateMap_AsteriskFieldIsMissing(t *testing.T) {
}
}`)
var pattern, resource interface{}
var pattern, resource map[string]interface{}
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.Assert(t, validateMap(resource, pattern) != nil)
res := validateMap(resource, pattern, "/")
assert.Assert(t, res.ToError() != nil)
}
func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) {
@ -724,7 +729,8 @@ func TestValidateMapElement_TwoElementsInArrayOnePass(t *testing.T) {
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.NilError(t, validateMapElement(resource, pattern))
res := validateResourceElement(resource, pattern, "/")
assert.NilError(t, res.ToError())
}
func TestValidateMapElement_OneElementInArrayPass(t *testing.T) {
@ -755,7 +761,8 @@ func TestValidateMapElement_OneElementInArrayPass(t *testing.T) {
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.NilError(t, validateMapElement(resource, pattern))
res := validateResourceElement(resource, pattern, "/")
assert.NilError(t, res.ToError())
}
func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) {
@ -786,7 +793,8 @@ func TestValidateMapElement_OneElementInArrayNotPass(t *testing.T) {
json.Unmarshal(rawPattern, &pattern)
json.Unmarshal(rawMap, &resource)
assert.Assert(t, validateMapElement(resource, pattern) != nil)
res := validateResourceElement(resource, pattern, "/")
assert.Assert(t, res.ToError() != nil)
}
func TestValidate_ServiceTest(t *testing.T) {
@ -977,5 +985,6 @@ func TestValidate_MapHasFloats(t *testing.T) {
Kind: "Deployment",
}
assert.NilError(t, Validate(policy, rawResource, gvk))
res := Validate(policy, rawResource, gvk)
assert.NilError(t, res.ToError())
}

View file

@ -9,6 +9,7 @@ import (
policyscheme "github.com/nirmata/kyverno/pkg/client/clientset/versioned/scheme"
v1alpha1 "github.com/nirmata/kyverno/pkg/client/listers/policy/v1alpha1"
client "github.com/nirmata/kyverno/pkg/dclient"
"github.com/nirmata/kyverno/pkg/result"
"github.com/nirmata/kyverno/pkg/sharedinformer"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
@ -157,7 +158,7 @@ func (c *controller) SyncHandler(key Info) error {
}
//NewEvent returns a new event
func NewEvent(kind string, resource string, reason Reason, message MsgKey, args ...interface{}) Info {
func NewEvent(kind string, resource string, reason result.Reason, message MsgKey, args ...interface{}) Info {
msgText, err := getEventMsg(message, args)
if err != nil {
utilruntime.HandleError(err)

View file

@ -95,12 +95,18 @@ func applyPolicy(policy *kubepolicy.Policy, resources []*resourceInfo) (output s
}
func applyPolicyOnRaw(policy *kubepolicy.Policy, rawResource []byte, gvk *metav1.GroupVersionKind) ([]byte, error) {
_, patchedDocument := engine.Mutate(*policy, rawResource, *gvk)
patches, result := engine.Mutate(*policy, rawResource, *gvk)
if err := engine.Validate(*policy, patchedDocument, *gvk); err != nil {
return nil, err
err := result.ToError()
var patchedResource []byte
if err == nil {
patchedResource, err = engine.ApplyPatches(rawResource, patches)
if err != nil {
return nil, fmt.Errorf("Unable to apply mutation patches:\n%v", err)
}
err = engine.Validate(*policy, patchedResource, *gvk).ToError()
}
return patchedDocument, nil
return patchedResource, err
}
func extractPolicy(fileDir string) (*kubepolicy.Policy, error) {

21
pkg/result/reason.go Normal file
View file

@ -0,0 +1,21 @@
package result
//Reason types of Result Reasons
type Reason int
const (
//PolicyViolation there is a violation of policy
Success Reason = iota
//Success policy applied
Violation
//Failed the request to create/update the resource was blocked(generated from admission-controller)
Failed
)
func (r Reason) String() string {
return [...]string{
"Success",
"Violation",
"Failed",
}[r]
}

175
pkg/result/result.go Normal file
View file

@ -0,0 +1,175 @@
package result
import (
"fmt"
)
// Indent acts for indenting in result hierarchy
type Indent string
const (
// SpaceIndent means 4 spaces
SpaceIndent Indent = " "
// TabIndent is a tab symbol
TabIndent Indent = "\t"
)
// Result is an interface that is used for result polymorphic behavio
type Result interface {
String() string
StringWithIndent(indent string) string
GetReason() Reason
ToError() error
}
// CompositeResult is used for result hierarchy
type CompositeResult struct {
Message string
Reason Reason
Children []Result
}
// RuleApplicationResult represents elementary result that is produced by PolicyEngine
// TODO: It can be used to create Kubernetes Results, so make method for this
type RuleApplicationResult struct {
PolicyRule string
Reason Reason
Messages []string
}
func NewRuleApplicationResult(ruleName string) RuleApplicationResult {
return RuleApplicationResult{
PolicyRule: ruleName,
Reason: Success,
Messages: []string{},
}
}
// StringWithIndent makes result string where each
// line is prepended with specified indent
func (e *RuleApplicationResult) StringWithIndent(indent string) string {
message := fmt.Sprintf("%s* %s: policy rule - %s:\n", indent, e.Reason.String(), e.PolicyRule)
childrenIndent := indent + string(SpaceIndent)
for i, m := range e.Messages {
message += fmt.Sprintf("%s%d. %s\n", childrenIndent, i+1, m)
}
// remove last line feed
if 0 != len(message) {
message = message[:len(message)-1]
}
return message
}
// String makes result string
// for writing it to logs
func (e *RuleApplicationResult) String() string {
return e.StringWithIndent("")
}
func (e *RuleApplicationResult) ToError() error {
if e.Reason != Success {
return fmt.Errorf(e.String())
}
return nil
}
func (e *RuleApplicationResult) GetReason() Reason {
return e.Reason
}
// Adds formatted message to this result
func (rar *RuleApplicationResult) AddMessagef(message string, a ...interface{}) {
rar.Messages = append(rar.Messages, fmt.Sprintf(message, a...))
}
// Sets the Reason Failed and adds formatted message to this result
func (rar *RuleApplicationResult) FailWithMessagef(message string, a ...interface{}) {
rar.Reason = Failed
rar.AddMessagef(message, a...)
}
// Takes messages and higher reason from another RuleApplicationResult
func (e *RuleApplicationResult) MergeWith(other *RuleApplicationResult) {
if other != nil {
e.Messages = append(e.Messages, other.Messages...)
}
if other.Reason > e.Reason {
e.Reason = other.Reason
}
}
// StringWithIndent makes result string where each
// line is prepended with specified indent
func (e *CompositeResult) StringWithIndent(indent string) string {
childrenIndent := indent + string(SpaceIndent)
message := fmt.Sprintf("%s- %s: %s\n", indent, e.Reason, e.Message)
for _, res := range e.Children {
message += (res.StringWithIndent(childrenIndent) + "\n")
}
// remove last line feed
if 0 != len(message) {
message = message[:len(message)-1]
}
return message
}
// String makes result string
// for writing it to logs
func (e *CompositeResult) String() string {
return e.StringWithIndent("")
}
func (e *CompositeResult) ToError() error {
if e.Reason != Success {
return fmt.Errorf(e.String())
}
return nil
}
func (e *CompositeResult) GetReason() Reason {
return e.Reason
}
func NewPolicyApplicationResult(policyName string) Result {
return &CompositeResult{
Message: fmt.Sprintf("policy - %s:", policyName),
Reason: Success,
}
}
func NewAdmissionResult(requestUID string) Result {
return &CompositeResult{
Message: fmt.Sprintf("For resource with UID - %s:", requestUID),
Reason: Success,
}
}
// Append returns CompositeResult with target and source
// Or appends source to target if it is composite result
// If the source reason is more important than target reason,
// target takes the reason of the source.
func Append(target Result, source Result) Result {
targetReason := target.GetReason()
if targetReason < source.GetReason() {
targetReason = source.GetReason()
}
if composite, ok := target.(*CompositeResult); ok {
composite.Children = append(composite.Children, source)
composite.Reason = targetReason
return composite
}
composite := &CompositeResult{
Children: []Result{
target,
source,
},
Reason: targetReason,
}
return composite
}

54
pkg/result/result_test.go Normal file
View file

@ -0,0 +1,54 @@
package result
import (
"testing"
"gotest.tools/assert"
)
func TestAppend_TwoResultObjects(t *testing.T) {
firstRuleApplicationResult := RuleApplicationResult{
Reason: Failed,
Messages: []string{
"1. Test",
"2. Toast",
},
}
secondRuleApplicationResult := RuleApplicationResult{
Reason: Success,
Messages: []string{
"1. Kyverno",
"2. KubePolicy",
},
}
result := Append(&firstRuleApplicationResult, &secondRuleApplicationResult)
composite, ok := result.(*CompositeResult)
assert.Assert(t, ok)
assert.Equal(t, len(composite.Children), 2)
RuleApplicationResult, ok := composite.Children[0].(*RuleApplicationResult)
assert.Assert(t, ok)
assert.Equal(t, RuleApplicationResult.Messages[1], "2. Toast")
}
func TestAppend_FirstObjectIsComposite(t *testing.T) {
composite := &CompositeResult{}
firstRuleApplicationResult := RuleApplicationResult{
Reason: Failed,
Messages: []string{
"1. Test",
"2. Toast",
},
}
result := Append(composite, &firstRuleApplicationResult)
composite, ok := result.(*CompositeResult)
assert.Equal(t, len(composite.Children), 1)
RuleApplicationResult, ok := composite.Children[0].(*RuleApplicationResult)
assert.Assert(t, ok)
assert.Equal(t, RuleApplicationResult.Messages[1], "2. Toast")
}

View file

@ -15,6 +15,7 @@ import (
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
engine "github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/result"
"github.com/nirmata/kyverno/pkg/sharedinformer"
tlsutils "github.com/nirmata/kyverno/pkg/tls"
v1beta1 "k8s.io/api/admission/v1beta1"
@ -131,13 +132,19 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
return nil
}
admissionResult := result.NewAdmissionResult(string(request.UID))
var allPatches []engine.PatchBytes
for _, policy := range policies {
glog.Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
policyPatches, _ := engine.Mutate(*policy, request.Object.Raw, request.Kind)
policyPatches, mutationResult := engine.Mutate(*policy, request.Object.Raw, request.Kind)
allPatches = append(allPatches, policyPatches...)
admissionResult = result.Append(admissionResult, mutationResult)
if mutationError := mutationResult.ToError(); mutationError != nil {
glog.Warningf(mutationError.Error())
}
if len(policyPatches) > 0 {
namespace := engine.ParseNamespaceFromObject(request.Object.Raw)
@ -146,11 +153,23 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
}
}
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
Allowed: true,
Patch: engine.JoinPatches(allPatches),
PatchType: &patchType,
message := admissionResult.String()
glog.Info(message)
if admissionResult.GetReason() == result.Success {
patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{
Allowed: true,
Patch: engine.JoinPatches(allPatches),
PatchType: &patchType,
}
} else {
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: message,
},
}
}
}
@ -165,30 +184,42 @@ func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1
return nil
}
admissionResult := result.NewAdmissionResult(string(request.UID))
for _, policy := range policies {
// validation
glog.Infof("Validating resource with policy %s with %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
validationResult := engine.Validate(*policy, request.Object.Raw, request.Kind)
admissionResult = result.Append(admissionResult, validationResult)
if err := engine.Validate(*policy, request.Object.Raw, request.Kind); err != nil {
message := fmt.Sprintf("validation has failed: %s", err.Error())
glog.Warning(message)
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: message,
},
}
if validationError := validationResult.ToError(); validationError != nil {
glog.Warningf(validationError.Error())
}
// generation
engine.Generate(ws.client, *policy, request.Object.Raw, request.Kind)
}
glog.Info("Validation is successful")
return &v1beta1.AdmissionResponse{
Allowed: true,
}
message := admissionResult.String()
glog.Info(message)
// Generation loop after all validation succeeded
var response *v1beta1.AdmissionResponse
if admissionResult.GetReason() == result.Success {
for _, policy := range policies {
engine.Generate(ws.client, *policy, request.Object.Raw, request.Kind)
}
glog.Info("Validation is successful")
response = &v1beta1.AdmissionResponse{
Allowed: true,
}
} else {
response = &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
Message: message,
},
}
}
return response
}
// bodyToAdmissionReview creates AdmissionReview object from request body