mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-14 11:57:48 +00:00
introduce info struct for engine responses
This commit is contained in:
parent
1f8b370f5a
commit
b7655ae747
9 changed files with 659 additions and 418 deletions
|
@ -1,9 +1,8 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/result"
|
||||
)
|
||||
|
||||
// CreateAnchorHandler is a factory that create anchor handlers
|
||||
|
@ -23,7 +22,7 @@ func CreateAnchorHandler(anchor string, pattern interface{}, path string) Valida
|
|||
// resourcePart must be an array of dictionaries
|
||||
// patternPart must be a dictionary with anchors
|
||||
type ValidationAnchorHandler interface {
|
||||
Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult
|
||||
Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error
|
||||
}
|
||||
|
||||
// NoAnchorValidationHandler just calls validateMap
|
||||
|
@ -41,23 +40,23 @@ func NewNoAnchorValidationHandler(path string) ValidationAnchorHandler {
|
|||
}
|
||||
|
||||
// Handle performs validation in context of NoAnchorValidationHandler
|
||||
func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult {
|
||||
handlingResult := result.NewRuleApplicationResult("")
|
||||
func (navh *NoAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error {
|
||||
|
||||
for i, resourceElement := range resourcePart {
|
||||
currentPath := navh.path + strconv.Itoa(i) + "/"
|
||||
|
||||
typedResourceElement, ok := resourceElement.(map[string]interface{})
|
||||
if !ok {
|
||||
handlingResult.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
|
||||
return handlingResult
|
||||
return fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
|
||||
}
|
||||
|
||||
res := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
|
||||
handlingResult.MergeWith(&res)
|
||||
err := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return handlingResult
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConditionAnchorValidationHandler performs
|
||||
|
@ -81,7 +80,7 @@ func NewConditionAnchorValidationHandler(anchor string, pattern interface{}, pat
|
|||
}
|
||||
|
||||
// Handle performs validation in context of ConditionAnchorValidationHandler
|
||||
func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult {
|
||||
func (cavh *ConditionAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error {
|
||||
_, handlingResult := handleConditionCases(resourcePart, patternPart, cavh.anchor, cavh.pattern, cavh.path, originPattern)
|
||||
|
||||
return handlingResult
|
||||
|
@ -110,14 +109,16 @@ func NewExistanceAnchorValidationHandler(anchor string, pattern interface{}, pat
|
|||
}
|
||||
|
||||
// Handle performs validation in context of ExistanceAnchorValidationHandler
|
||||
func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) result.RuleApplicationResult {
|
||||
anchoredEntries, handlingResult := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path, originPattern)
|
||||
|
||||
func (eavh *ExistanceAnchorValidationHandler) Handle(resourcePart []interface{}, patternPart map[string]interface{}, originPattern interface{}) error {
|
||||
anchoredEntries, err := handleConditionCases(resourcePart, patternPart, eavh.anchor, eavh.pattern, eavh.path, originPattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if 0 == anchoredEntries {
|
||||
handlingResult.FailWithMessagef("Existance anchor %s used, but no suitable entries were found", eavh.anchor)
|
||||
return fmt.Errorf("Existance anchor %s used, but no suitable entries were found", eavh.anchor)
|
||||
}
|
||||
|
||||
return handlingResult
|
||||
return nil
|
||||
}
|
||||
|
||||
// check if array element fits the anchor
|
||||
|
@ -134,8 +135,7 @@ func checkForAnchorCondition(anchor string, pattern interface{}, resourceMap map
|
|||
// both () and ^() are checking conditions and have a lot of similar logic
|
||||
// the only difference is that ^() requires existace of one element
|
||||
// anchoredEntries var counts this occurences.
|
||||
func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string, originPattern interface{}) (int, result.RuleApplicationResult) {
|
||||
handlingResult := result.NewRuleApplicationResult("")
|
||||
func handleConditionCases(resourcePart []interface{}, patternPart map[string]interface{}, anchor string, pattern interface{}, path string, originPattern interface{}) (int, error) {
|
||||
anchoredEntries := 0
|
||||
|
||||
for i, resourceElement := range resourcePart {
|
||||
|
@ -143,8 +143,7 @@ func handleConditionCases(resourcePart []interface{}, patternPart map[string]int
|
|||
|
||||
typedResourceElement, ok := resourceElement.(map[string]interface{})
|
||||
if !ok {
|
||||
handlingResult.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
|
||||
break
|
||||
return 0, fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", currentPath, patternPart, resourceElement)
|
||||
}
|
||||
|
||||
if !checkForAnchorCondition(anchor, pattern, typedResourceElement) {
|
||||
|
@ -152,9 +151,11 @@ func handleConditionCases(resourcePart []interface{}, patternPart map[string]int
|
|||
}
|
||||
|
||||
anchoredEntries++
|
||||
res := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
|
||||
handlingResult.MergeWith(&res)
|
||||
err := validateMap(typedResourceElement, patternPart, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return anchoredEntries, handlingResult
|
||||
return anchoredEntries, nil
|
||||
}
|
||||
|
|
|
@ -6,20 +6,22 @@ import (
|
|||
"github.com/golang/glog"
|
||||
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
||||
client "github.com/nirmata/kyverno/pkg/dclient"
|
||||
"github.com/nirmata/kyverno/pkg/info"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Generate should be called to process generate rules on the resource
|
||||
func Generate(client *client.Client, policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) {
|
||||
// configMapGenerator and secretGenerator can be applied only to namespaces
|
||||
// TODO: support for any resource
|
||||
if gvk.Kind != "Namespace" {
|
||||
return
|
||||
}
|
||||
func Generate(client *client.Client, policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) []*info.RuleInfo {
|
||||
ris := []*info.RuleInfo{}
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
||||
if rule.Generation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ri := info.NewRuleInfo(rule.Name)
|
||||
|
||||
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
||||
if !ok {
|
||||
glog.Infof("Rule is not applicable to the request: rule name = %s in policy %s \n", rule.Name, policy.ObjectMeta.Name)
|
||||
continue
|
||||
|
@ -27,17 +29,17 @@ func Generate(client *client.Client, policy kubepolicy.Policy, rawResource []byt
|
|||
|
||||
err := applyRuleGenerator(client, rawResource, rule.Generation, gvk)
|
||||
if err != nil {
|
||||
glog.Warningf("Failed to apply rule generator: %v", err)
|
||||
ri.Fail()
|
||||
ri.Addf(" Failed to apply rule generator. err %v", err)
|
||||
} else {
|
||||
ri.Add("Generation succesfully")
|
||||
}
|
||||
ris = append(ris, ri)
|
||||
}
|
||||
return ris
|
||||
}
|
||||
|
||||
// Applies "configMapGenerator" and "secretGenerator" described in PolicyRule
|
||||
// TODO: plan to support all kinds of generator
|
||||
func applyRuleGenerator(client *client.Client, rawResource []byte, generator *kubepolicy.Generation, gvk metav1.GroupVersionKind) error {
|
||||
if generator == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var err error
|
||||
|
||||
|
|
|
@ -1,51 +1,60 @@
|
|||
package engine
|
||||
|
||||
import (
|
||||
"github.com/golang/glog"
|
||||
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
||||
"github.com/nirmata/kyverno/pkg/result"
|
||||
"github.com/nirmata/kyverno/pkg/info"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Mutate performs mutation. Overlay first and then mutation patches
|
||||
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, result.Result) {
|
||||
func Mutate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, []*info.RuleInfo) {
|
||||
var allPatches []PatchBytes
|
||||
|
||||
patchedDocument := rawResource
|
||||
policyResult := result.NewPolicyApplicationResult(policy.Name)
|
||||
ris := []*info.RuleInfo{}
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if rule.Mutation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ruleApplicationResult := result.NewRuleApplicationResult(rule.Name)
|
||||
ri := info.NewRuleInfo(rule.Name)
|
||||
|
||||
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
||||
if !ok {
|
||||
ruleApplicationResult.AddMessagef("Rule %s is not applicable to resource\n", rule.Name)
|
||||
} else {
|
||||
glog.V(3).Info("Not applicable on specified resource kind%s", gvk.Kind)
|
||||
continue
|
||||
}
|
||||
// Process Overlay
|
||||
overlayPatches, ruleResult := ProcessOverlay(rule, 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)
|
||||
if rule.Mutation.Overlay != nil {
|
||||
overlayPatches, err := ProcessOverlay(rule, rawResource, gvk)
|
||||
if err != nil {
|
||||
ri.Fail()
|
||||
ri.Addf("Overlay application has failed. err %s", err)
|
||||
} else {
|
||||
ruleApplicationResult.AddMessagef("Success")
|
||||
ri.Add("Overlay succesfully applied")
|
||||
//TODO: patchbytes -> string
|
||||
//glog.V(3).Info(" Overlay succesfully applied. Patch %s", string(overlayPatches))
|
||||
allPatches = append(allPatches, overlayPatches...)
|
||||
}
|
||||
}
|
||||
|
||||
// Process Patches
|
||||
rulePatches, ruleResult := ProcessPatches(rule, 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)
|
||||
if len(rule.Mutation.Patches) != 0 {
|
||||
rulePatches, errs := ProcessPatches(rule, patchedDocument)
|
||||
if len(errs) > 0 {
|
||||
ri.Fail()
|
||||
for _, err := range errs {
|
||||
ri.Addf("Patches application has failed. err %s", err)
|
||||
}
|
||||
} else {
|
||||
ruleApplicationResult.AddMessagef("Success")
|
||||
ri.Add("Patches succesfully applied")
|
||||
//TODO: patchbytes -> string
|
||||
//glog.V(3).Info("Patches succesfully applied. Patch %s", string(overlayPatches))
|
||||
allPatches = append(allPatches, rulePatches...)
|
||||
}
|
||||
}
|
||||
policyResult = result.Append(policyResult, &ruleApplicationResult)
|
||||
ris = append(ris, ri)
|
||||
}
|
||||
|
||||
return allPatches, policyResult
|
||||
return allPatches, ris
|
||||
}
|
||||
|
|
|
@ -2,109 +2,99 @@ package engine
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
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 resources for rules defined in the policy
|
||||
func ProcessOverlay(rule kubepolicy.Rule, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
overlayApplicationResult := result.NewRuleApplicationResult(rule.Name)
|
||||
if rule.Mutation == nil || rule.Mutation.Overlay == nil {
|
||||
return nil, overlayApplicationResult
|
||||
}
|
||||
func ProcessOverlay(rule kubepolicy.Rule, rawResource []byte, gvk metav1.GroupVersionKind) ([]PatchBytes, error) {
|
||||
|
||||
var resource interface{}
|
||||
var appliedPatches []PatchBytes
|
||||
json.Unmarshal(rawResource, &resource)
|
||||
|
||||
patches, res := mutateResourceWithOverlay(resource, *rule.Mutation.Overlay)
|
||||
overlayApplicationResult.MergeWith(&res)
|
||||
|
||||
if overlayApplicationResult.GetReason() == result.Success {
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
err := json.Unmarshal(rawResource, &resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return appliedPatches, overlayApplicationResult
|
||||
patches, err := mutateResourceWithOverlay(resource, *rule.Mutation.Overlay)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
|
||||
return appliedPatches, err
|
||||
}
|
||||
|
||||
// mutateResourceWithOverlay is a start of overlaying process
|
||||
func mutateResourceWithOverlay(resource, pattern interface{}) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func mutateResourceWithOverlay(resource, pattern interface{}) ([]PatchBytes, error) {
|
||||
// It assumes that mutation is started from root, so "/" is passed
|
||||
return applyOverlay(resource, pattern, "/")
|
||||
}
|
||||
|
||||
// applyOverlay detects type of current item and goes down through overlay and resource trees applying overlay
|
||||
func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func applyOverlay(resource, overlay interface{}, path string) ([]PatchBytes, error) {
|
||||
var appliedPatches []PatchBytes
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
|
||||
// resource item exists but has different type - replace
|
||||
// all subtree within this path by overlay
|
||||
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
|
||||
patch, res := replaceSubtree(overlay, path)
|
||||
overlayResult.MergeWith(&res)
|
||||
patch, err := replaceSubtree(overlay, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
appliedPatches = append(appliedPatches, patch)
|
||||
}
|
||||
return appliedPatches, overlayResult
|
||||
}
|
||||
|
||||
return applyOverlayForSameTypes(resource, overlay, path)
|
||||
}
|
||||
|
||||
// applyOverlayForSameTypes is applyOverlay for cases when TypeOf(resource) == TypeOf(overlay)
|
||||
func applyOverlayForSameTypes(resource, overlay interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func applyOverlayForSameTypes(resource, overlay interface{}, path string) ([]PatchBytes, error) {
|
||||
var appliedPatches []PatchBytes
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
|
||||
// detect the type of resource and overlay and select corresponding handler
|
||||
switch typedOverlay := overlay.(type) {
|
||||
// map
|
||||
case map[string]interface{}:
|
||||
typedResource := resource.(map[string]interface{})
|
||||
patches, res := applyOverlayToMap(typedResource, typedOverlay, path)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
patches, err := applyOverlayToMap(typedResource, typedOverlay, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
// array
|
||||
case []interface{}:
|
||||
typedResource := resource.([]interface{})
|
||||
patches, res := applyOverlayToArray(typedResource, typedOverlay, path)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
patches, err := applyOverlayToArray(typedResource, typedOverlay, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
// elementary types
|
||||
case string, float64, int64, bool:
|
||||
patch, res := replaceSubtree(overlay, path)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
patch, err := replaceSubtree(overlay, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patch)
|
||||
}
|
||||
default:
|
||||
overlayResult.FailWithMessagef("Overlay has unsupported type: %T", overlay)
|
||||
return nil, overlayResult
|
||||
return nil, fmt.Errorf("Overlay has unsupported type: %T", overlay)
|
||||
}
|
||||
|
||||
return appliedPatches, overlayResult
|
||||
return appliedPatches, nil
|
||||
}
|
||||
|
||||
// for each overlay and resource map elements applies overlay
|
||||
func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path string) ([]PatchBytes, error) {
|
||||
var appliedPatches []PatchBytes
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
|
||||
for key, value := range overlayMap {
|
||||
// skip anchor element because it has condition, not
|
||||
|
@ -119,62 +109,55 @@ func applyOverlayToMap(resourceMap, overlayMap map[string]interface{}, path stri
|
|||
|
||||
if ok && !isAddingAnchor(key) {
|
||||
// Key exists - go down through the overlay and resource trees
|
||||
patches, res := applyOverlay(resourcePart, value, currentPath)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
patches, err := applyOverlay(resourcePart, value, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
}
|
||||
|
||||
if !ok {
|
||||
// Key does not exist - insert entire overlay subtree
|
||||
patch, res := insertSubtree(value, currentPath)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
patch, err := insertSubtree(value, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return appliedPatches, overlayResult
|
||||
return appliedPatches, nil
|
||||
}
|
||||
|
||||
// for each overlay and resource array elements applies overlay
|
||||
func applyOverlayToArray(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func applyOverlayToArray(resource, overlay []interface{}, path string) ([]PatchBytes, error) {
|
||||
var appliedPatches []PatchBytes
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
|
||||
if 0 == len(overlay) {
|
||||
overlayResult.FailWithMessagef("Empty array detected in the overlay")
|
||||
return nil, overlayResult
|
||||
return nil, errors.New("Empty array detected in the overlay")
|
||||
}
|
||||
|
||||
if 0 == len(resource) {
|
||||
// If array resource is empty, insert part from overlay
|
||||
patch, res := insertSubtree(overlay, path)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
appliedPatches = append(appliedPatches, patch)
|
||||
patch, err := insertSubtree(overlay, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patch)
|
||||
|
||||
return appliedPatches, res
|
||||
return appliedPatches, nil
|
||||
}
|
||||
|
||||
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
|
||||
overlayResult.FailWithMessagef("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
|
||||
return nil, overlayResult
|
||||
return nil, fmt.Errorf("Overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
|
||||
}
|
||||
|
||||
return applyOverlayToArrayOfSameTypes(resource, overlay, path)
|
||||
}
|
||||
|
||||
// applyOverlayToArrayOfSameTypes applies overlay to array elements if they (resource and overlay elements) have same type
|
||||
func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string) ([]PatchBytes, error) {
|
||||
var appliedPatches []PatchBytes
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
|
||||
switch overlay[0].(type) {
|
||||
case map[string]interface{}:
|
||||
|
@ -186,22 +169,20 @@ func applyOverlayToArrayOfSameTypes(resource, overlay []interface{}, path string
|
|||
for i, value := range overlay {
|
||||
currentPath := path + strconv.Itoa(lastElementIdx+i) + "/"
|
||||
// currentPath example: /spec/template/spec/containers/3/
|
||||
patch, res := insertSubtree(value, currentPath)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
patch, err := insertSubtree(value, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patch)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return appliedPatches, overlayResult
|
||||
return appliedPatches, nil
|
||||
}
|
||||
|
||||
// Array of maps needs special handling as far as it can have anchors.
|
||||
func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]PatchBytes, error) {
|
||||
var appliedPatches []PatchBytes
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
|
||||
lastElementIdx := len(resource)
|
||||
for i, overlayElement := range overlay {
|
||||
|
@ -210,43 +191,39 @@ func applyOverlayToArrayOfMaps(resource, overlay []interface{}, path string) ([]
|
|||
|
||||
if len(anchors) > 0 {
|
||||
// If we have anchors - choose corresponding resource element and mutate it
|
||||
patches, res := applyOverlayWithAnchors(resource, overlayElement, anchors, path)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
patches, err := applyOverlayWithAnchors(resource, overlayElement, anchors, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
} else if hasNestedAnchors(overlayElement) {
|
||||
// If we have anchors on the lower level - continue traversing overlay and resource trees
|
||||
for j, resourceElement := range resource {
|
||||
currentPath := path + strconv.Itoa(j) + "/"
|
||||
// currentPath example: /spec/template/spec/containers/3/
|
||||
patches, res := applyOverlay(resourceElement, overlayElement, currentPath)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
patches, err := applyOverlay(resourceElement, overlayElement, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
}
|
||||
} else {
|
||||
// Overlay subtree has no anchors - insert new element
|
||||
currentPath := path + strconv.Itoa(lastElementIdx+i) + "/"
|
||||
// currentPath example: /spec/template/spec/containers/3/
|
||||
patch, res := insertSubtree(overlayElement, currentPath)
|
||||
overlayResult.MergeWith(&res)
|
||||
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
patch, err := insertSubtree(overlayElement, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patch)
|
||||
}
|
||||
}
|
||||
|
||||
return appliedPatches, nil
|
||||
}
|
||||
|
||||
return appliedPatches, overlayResult
|
||||
}
|
||||
|
||||
func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchors map[string]interface{}, path string) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchors map[string]interface{}, path string) ([]PatchBytes, error) {
|
||||
var appliedPatches []PatchBytes
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
|
||||
for i, resourceElement := range resource {
|
||||
typedResource := resourceElement.(map[string]interface{})
|
||||
|
@ -254,27 +231,26 @@ func applyOverlayWithAnchors(resource []interface{}, overlay interface{}, anchor
|
|||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
// currentPath example: /spec/template/spec/containers/3/
|
||||
if !skipArrayObject(typedResource, anchors) {
|
||||
patches, res := applyOverlay(resourceElement, overlay, currentPath)
|
||||
overlayResult.MergeWith(&res)
|
||||
if result.Success == overlayResult.GetReason() {
|
||||
patches, err := applyOverlay(resourceElement, overlay, currentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
appliedPatches = append(appliedPatches, patches...)
|
||||
}
|
||||
}
|
||||
|
||||
return appliedPatches, nil
|
||||
}
|
||||
|
||||
return appliedPatches, overlayResult
|
||||
}
|
||||
|
||||
func insertSubtree(overlay interface{}, path string) (PatchBytes, result.RuleApplicationResult) {
|
||||
func insertSubtree(overlay interface{}, path string) (PatchBytes, error) {
|
||||
return processSubtree(overlay, path, "add")
|
||||
}
|
||||
|
||||
func replaceSubtree(overlay interface{}, path string) (PatchBytes, result.RuleApplicationResult) {
|
||||
func replaceSubtree(overlay interface{}, path string) (PatchBytes, error) {
|
||||
return processSubtree(overlay, path, "replace")
|
||||
}
|
||||
|
||||
func processSubtree(overlay interface{}, path string, op string) (PatchBytes, result.RuleApplicationResult) {
|
||||
overlayResult := result.NewRuleApplicationResult("")
|
||||
func processSubtree(overlay interface{}, path string, op string) (PatchBytes, error) {
|
||||
|
||||
if len(path) > 1 && path[len(path)-1] == '/' {
|
||||
path = path[:len(path)-1]
|
||||
|
@ -290,11 +266,11 @@ func processSubtree(overlay interface{}, path string, op string) (PatchBytes, re
|
|||
// check the patch
|
||||
_, err := jsonpatch.DecodePatch([]byte("[" + patchStr + "]"))
|
||||
if err != nil {
|
||||
overlayResult.FailWithMessagef("Failed to make '%s' patch from an overlay '%s' for path %s", op, value, path)
|
||||
return nil, overlayResult
|
||||
glog.V(3).Info(err)
|
||||
return nil, fmt.Errorf("Failed to make '%s' patch from an overlay '%s' for path %s", op, value, path)
|
||||
}
|
||||
|
||||
return PatchBytes(patchStr), overlayResult
|
||||
return PatchBytes(patchStr), nil
|
||||
}
|
||||
|
||||
// converts overlay to JSON string to be inserted into the JSON Patch
|
||||
|
@ -302,6 +278,7 @@ func prepareJSONValue(overlay interface{}) string {
|
|||
jsonOverlay, err := json.Marshal(overlay)
|
||||
|
||||
if err != nil || hasOnlyAnchors(overlay) {
|
||||
glog.V(3).Info(err)
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
|
@ -2,11 +2,10 @@ package engine
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"errors"
|
||||
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
||||
"github.com/nirmata/kyverno/pkg/result"
|
||||
)
|
||||
|
||||
// PatchBytes stands for []byte
|
||||
|
@ -14,40 +13,27 @@ 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(rule kubepolicy.Rule, resource []byte) ([]PatchBytes, result.RuleApplicationResult) {
|
||||
res := result.NewRuleApplicationResult(rule.Name)
|
||||
if rule.Mutation == nil || len(rule.Mutation.Patches) == 0 {
|
||||
return nil, res
|
||||
}
|
||||
|
||||
func ProcessPatches(rule kubepolicy.Rule, resource []byte) (allPatches []PatchBytes, errs []error) {
|
||||
if len(resource) == 0 {
|
||||
res.AddMessagef("Source document for patching is empty")
|
||||
res.Reason = result.Failed
|
||||
return nil, res
|
||||
errs = append(errs, errors.New("Source document for patching is empty"))
|
||||
return nil, errs
|
||||
}
|
||||
|
||||
var allPatches []PatchBytes
|
||||
patchedDocument := resource
|
||||
for i, patch := range rule.Mutation.Patches {
|
||||
for _, patch := range rule.Mutation.Patches {
|
||||
patchRaw, err := json.Marshal(patch)
|
||||
if err != nil {
|
||||
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
|
||||
patchedDocument, err = applyPatch(patchedDocument, patchRaw)
|
||||
if err != nil {
|
||||
// TODO: continue on error if one of the patches fails, will add the failure event in such case
|
||||
if patch.Operation == "remove" {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
message := fmt.Sprintf("Patch failed: patch number = %d, patch Operation = %s, err: %v", i, patch.Operation, err)
|
||||
res.Messages = append(res.Messages, message)
|
||||
continue
|
||||
}
|
||||
|
||||
allPatches = append(allPatches, patchRaw)
|
||||
}
|
||||
return allPatches, res
|
||||
return allPatches, errs
|
||||
}
|
||||
|
||||
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
|
||||
|
|
|
@ -2,73 +2,73 @@ package engine
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
|
||||
"github.com/nirmata/kyverno/pkg/result"
|
||||
"github.com/nirmata/kyverno/pkg/info"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// Validate handles validating admission request
|
||||
// Checks the target resources for rules defined in the policy
|
||||
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) result.Result {
|
||||
func Validate(policy kubepolicy.Policy, rawResource []byte, gvk metav1.GroupVersionKind) ([]*info.RuleInfo, error) {
|
||||
var resource interface{}
|
||||
json.Unmarshal(rawResource, &resource)
|
||||
ris := []*info.RuleInfo{}
|
||||
|
||||
policyResult := result.NewPolicyApplicationResult(policy.Name)
|
||||
err := json.Unmarshal(rawResource, &resource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rule := range policy.Spec.Rules {
|
||||
if rule.Validation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
ruleApplicationResult := result.NewRuleApplicationResult(rule.Name)
|
||||
ri := info.NewRuleInfo(rule.Name)
|
||||
|
||||
ok := ResourceMeetsDescription(rawResource, rule.ResourceDescription, gvk)
|
||||
if !ok {
|
||||
ruleApplicationResult.AddMessagef("Rule %s is not applicable to resource\n", rule.Name)
|
||||
policyResult = result.Append(policyResult, &ruleApplicationResult)
|
||||
glog.V(3).Info("Not applicable on specified resource kind%s", gvk.Kind)
|
||||
continue
|
||||
}
|
||||
|
||||
validationResult := validateResourceWithPattern(resource, rule.Validation.Pattern)
|
||||
if result.Success != validationResult.Reason {
|
||||
ruleApplicationResult.AddMessagef(*rule.Validation.Message)
|
||||
ruleApplicationResult.MergeWith(&validationResult)
|
||||
err := validateResourceWithPattern(resource, rule.Validation.Pattern)
|
||||
if err != nil {
|
||||
ri.Fail()
|
||||
ri.Addf("Validation has failed. err %s", err)
|
||||
} else {
|
||||
ruleApplicationResult.AddMessagef("Success")
|
||||
ri.Add("Validation succesfully")
|
||||
|
||||
}
|
||||
ris = append(ris, ri)
|
||||
}
|
||||
|
||||
policyResult = result.Append(policyResult, &ruleApplicationResult)
|
||||
}
|
||||
|
||||
return policyResult
|
||||
return ris, nil
|
||||
}
|
||||
|
||||
// validateResourceWithPattern is a start of element-by-element validation process
|
||||
// It assumes that validation is started from root, so "/" is passed
|
||||
func validateResourceWithPattern(resource, pattern interface{}) result.RuleApplicationResult {
|
||||
func validateResourceWithPattern(resource, pattern interface{}) error {
|
||||
return validateResourceElement(resource, pattern, pattern, "/")
|
||||
}
|
||||
|
||||
// validateResourceElement detects the element type (map, array, nil, string, int, bool, float)
|
||||
// and calls corresponding handler
|
||||
// Pattern tree and resource tree can have different structure. In this case validation fails
|
||||
func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) result.RuleApplicationResult {
|
||||
res := result.NewRuleApplicationResult("")
|
||||
// TODO: Move similar message templates to message package
|
||||
|
||||
func validateResourceElement(resourceElement, patternElement, originPattern interface{}, path string) error {
|
||||
var err error
|
||||
switch typedPatternElement := patternElement.(type) {
|
||||
// map
|
||||
case map[string]interface{}:
|
||||
typedResourceElement, ok := resourceElement.(map[string]interface{})
|
||||
if !ok {
|
||||
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
|
||||
return res
|
||||
return fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
|
||||
}
|
||||
|
||||
return validateMap(typedResourceElement, typedPatternElement, originPattern, path)
|
||||
|
@ -76,8 +76,7 @@ func validateResourceElement(resourceElement, patternElement, originPattern inte
|
|||
case []interface{}:
|
||||
typedResourceElement, ok := resourceElement.([]interface{})
|
||||
if !ok {
|
||||
res.FailWithMessagef("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
|
||||
return res
|
||||
return fmt.Errorf("Pattern and resource have different structures. Path: %s. Expected %T, found %T", path, patternElement, resourceElement)
|
||||
}
|
||||
|
||||
return validateArray(typedResourceElement, typedPatternElement, originPattern, path)
|
||||
|
@ -86,27 +85,25 @@ func validateResourceElement(resourceElement, patternElement, originPattern inte
|
|||
/*Analyze pattern */
|
||||
if checkedPattern := reflect.ValueOf(patternElement); checkedPattern.Kind() == reflect.String {
|
||||
if isStringIsReference(checkedPattern.String()) { //check for $ anchor
|
||||
patternElement, res = actualizePattern(originPattern, checkedPattern.String(), path)
|
||||
if result.Failed == res.Reason {
|
||||
return res
|
||||
patternElement, err = actualizePattern(originPattern, checkedPattern.String(), path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ValidateValueWithPattern(resourceElement, patternElement) {
|
||||
res.FailWithMessagef("Failed to validate value %v with pattern %v. Path: %s", resourceElement, patternElement, path)
|
||||
return fmt.Errorf("Failed to validate value %v with pattern %v. Path: %s", resourceElement, patternElement, path)
|
||||
}
|
||||
|
||||
return res
|
||||
default:
|
||||
res.FailWithMessagef("Pattern contains unknown type %T. Path: %s", patternElement, path)
|
||||
return res
|
||||
return fmt.Errorf("Pattern contains unknown type %T. Path: %s", patternElement, path)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// If validateResourceElement detects map element inside resource and pattern trees, it goes to validateMap
|
||||
// For each element of the map we must detect the type again, so we pass these elements to validateResourceElement
|
||||
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) result.RuleApplicationResult {
|
||||
res := result.NewRuleApplicationResult("")
|
||||
func validateMap(resourceMap, patternMap map[string]interface{}, origPattern interface{}, path string) error {
|
||||
|
||||
for key, patternElement := range patternMap {
|
||||
key = removeAnchor(key)
|
||||
|
@ -115,43 +112,47 @@ func validateMap(resourceMap, patternMap map[string]interface{}, origPattern int
|
|||
if patternElement == "*" && resourceMap[key] != nil {
|
||||
continue
|
||||
} else if patternElement == "*" && resourceMap[key] == nil {
|
||||
res.FailWithMessagef("Field %s is not present", key)
|
||||
return fmt.Errorf("Field %s is not present", key)
|
||||
} else {
|
||||
elementResult := validateResourceElement(resourceMap[key], patternElement, origPattern, path+key+"/")
|
||||
res.MergeWith(&elementResult)
|
||||
err := validateResourceElement(resourceMap[key], patternElement, origPattern, path+key+"/")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return nil
|
||||
}
|
||||
|
||||
func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) result.RuleApplicationResult {
|
||||
res := result.NewRuleApplicationResult("")
|
||||
func validateArray(resourceArray, patternArray []interface{}, originPattern interface{}, path string) error {
|
||||
|
||||
if 0 == len(patternArray) {
|
||||
return res
|
||||
return fmt.Errorf("Pattern Array empty")
|
||||
}
|
||||
|
||||
switch typedPatternElement := patternArray[0].(type) {
|
||||
case map[string]interface{}:
|
||||
// This is special case, because maps in arrays can have anchors that must be
|
||||
// processed with the special way affecting the entire array
|
||||
arrayResult := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path)
|
||||
res.MergeWith(&arrayResult)
|
||||
err := validateArrayOfMaps(resourceArray, typedPatternElement, originPattern, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
// In all other cases - detect type and handle each array element with validateResourceElement
|
||||
for i, patternElement := range patternArray {
|
||||
currentPath := path + strconv.Itoa(i) + "/"
|
||||
elementResult := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath)
|
||||
res.MergeWith(&elementResult)
|
||||
err := validateResourceElement(resourceArray[i], patternElement, originPattern, currentPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return res
|
||||
return nil
|
||||
}
|
||||
|
||||
func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, result.RuleApplicationResult) {
|
||||
res := result.NewRuleApplicationResult("")
|
||||
func actualizePattern(origPattern interface{}, referencePattern, absolutePath string) (interface{}, error) {
|
||||
var foundValue interface{}
|
||||
|
||||
referencePattern = strings.Trim(referencePattern, "$()")
|
||||
|
@ -160,41 +161,39 @@ func actualizePattern(origPattern interface{}, referencePattern, absolutePath st
|
|||
referencePattern = referencePattern[len(operator):]
|
||||
|
||||
if len(referencePattern) == 0 {
|
||||
res.FailWithMessagef("Expected path. Found empty reference")
|
||||
return nil, res
|
||||
return nil, errors.New("Expected path. Found empty reference")
|
||||
}
|
||||
|
||||
actualPath := FormAbsolutePath(referencePattern, absolutePath)
|
||||
|
||||
valFromReference, res := getValueFromReference(origPattern, actualPath)
|
||||
|
||||
if result.Failed == res.Reason {
|
||||
return nil, res
|
||||
valFromReference, err := getValueFromReference(origPattern, actualPath)
|
||||
if err != nil {
|
||||
return err, nil
|
||||
}
|
||||
|
||||
//TODO validate this
|
||||
if operator == Equal { //if operator does not exist return raw value
|
||||
return valFromReference, res
|
||||
return valFromReference, nil
|
||||
}
|
||||
|
||||
foundValue, res = valFromReferenceToString(valFromReference, string(operator))
|
||||
|
||||
return string(operator) + foundValue.(string), res
|
||||
foundValue, err = valFromReferenceToString(valFromReference, string(operator))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(operator) + foundValue.(string), nil
|
||||
}
|
||||
|
||||
//Parse value to string
|
||||
func valFromReferenceToString(value interface{}, operator string) (string, result.RuleApplicationResult) {
|
||||
res := result.NewRuleApplicationResult("")
|
||||
func valFromReferenceToString(value interface{}, operator string) (string, error) {
|
||||
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return typed, res
|
||||
return typed, nil
|
||||
case int, int64:
|
||||
return fmt.Sprintf("%d", value), res
|
||||
return fmt.Sprintf("%d", value), nil
|
||||
case float64:
|
||||
return fmt.Sprintf("%f", value), res
|
||||
return fmt.Sprintf("%f", value), nil
|
||||
default:
|
||||
res.FailWithMessagef("Incorrect expression. Operator %s does not match with value: %v", operator, value)
|
||||
return "", res
|
||||
return "", fmt.Errorf("Incorrect expression. Operator %s does not match with value: %v", operator, value)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,7 +206,7 @@ func FormAbsolutePath(referencePath, absolutePath string) string {
|
|||
}
|
||||
|
||||
//Prepares original pattern, path to value, and call traverse function
|
||||
func getValueFromReference(origPattern interface{}, reference string) (interface{}, result.RuleApplicationResult) {
|
||||
func getValueFromReference(origPattern interface{}, reference string) (interface{}, error) {
|
||||
originalPatternMap := origPattern.(map[string]interface{})
|
||||
reference = reference[1:len(reference)]
|
||||
statements := strings.Split(reference, "/")
|
||||
|
@ -215,14 +214,13 @@ func getValueFromReference(origPattern interface{}, reference string) (interface
|
|||
return getValueFromPattern(originalPatternMap, statements, 0)
|
||||
}
|
||||
|
||||
func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, result.RuleApplicationResult) {
|
||||
res := result.NewRuleApplicationResult("")
|
||||
func getValueFromPattern(patternMap map[string]interface{}, keys []string, currentKeyIndex int) (interface{}, error) {
|
||||
|
||||
for key, pattern := range patternMap {
|
||||
rawKey := getRawKeyIfWrappedWithAttributes(key)
|
||||
|
||||
if rawKey == keys[len(keys)-1] && currentKeyIndex == len(keys)-1 {
|
||||
return pattern, res
|
||||
return pattern, nil
|
||||
} else if rawKey != keys[currentKeyIndex] && currentKeyIndex != len(keys)-1 {
|
||||
continue
|
||||
}
|
||||
|
@ -233,21 +231,20 @@ func getValueFromPattern(patternMap map[string]interface{}, keys []string, curre
|
|||
for i, value := range typedPattern {
|
||||
resourceMap, ok := value.(map[string]interface{})
|
||||
if !ok {
|
||||
res.FailWithMessagef("Pattern and resource have different structures. Expected %T, found %T", pattern, value)
|
||||
return nil, res
|
||||
return nil, fmt.Errorf("Pattern and resource have different structures. Expected %T, found %T", pattern, value)
|
||||
}
|
||||
if keys[currentKeyIndex+1] == strconv.Itoa(i) {
|
||||
return getValueFromPattern(resourceMap, keys, currentKeyIndex+2)
|
||||
}
|
||||
res.FailWithMessagef("Reference to non-existent place in the document")
|
||||
return nil, errors.New("Reference to non-existent place in the document")
|
||||
}
|
||||
}
|
||||
res.FailWithMessagef("Reference to non-existent place in the document")
|
||||
return nil, errors.New("Reference to non-existent place in the document")
|
||||
case map[string]interface{}:
|
||||
if keys[currentKeyIndex] == rawKey {
|
||||
return getValueFromPattern(typedPattern, keys, currentKeyIndex+1)
|
||||
}
|
||||
res.FailWithMessagef("Reference to non-existent place in the document")
|
||||
return nil, errors.New("Reference to non-existent place in the document")
|
||||
case string, float64, int, int64, bool, nil:
|
||||
continue
|
||||
}
|
||||
|
@ -261,13 +258,12 @@ func getValueFromPattern(patternMap map[string]interface{}, keys []string, curre
|
|||
for _, elem := range keys {
|
||||
path = "/" + elem + path
|
||||
}
|
||||
res.FailWithMessagef("No value found for specified reference: %s", path)
|
||||
return nil, res
|
||||
return nil, fmt.Errorf("No value found for specified reference: %s", path)
|
||||
}
|
||||
|
||||
// validateArrayOfMaps gets anchors from pattern array map element, applies anchors logic
|
||||
// and then validates each map due to the pattern
|
||||
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) result.RuleApplicationResult {
|
||||
func validateArrayOfMaps(resourceMapArray []interface{}, patternMap map[string]interface{}, originPattern interface{}, path string) error {
|
||||
anchor, pattern := getAnchorFromMap(patternMap)
|
||||
|
||||
handler := CreateAnchorHandler(anchor, pattern, path)
|
||||
|
|
96
pkg/info/info.go
Normal file
96
pkg/info/info.go
Normal file
|
@ -0,0 +1,96 @@
|
|||
package info
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//PolicyInfo defines policy information
|
||||
type PolicyInfo struct {
|
||||
Name string
|
||||
Resource string
|
||||
Namespace string
|
||||
success bool
|
||||
rules []*RuleInfo
|
||||
}
|
||||
|
||||
//NewPolicyInfo returns a new policy info
|
||||
func NewPolicyInfo(policyName string, resource string, ns string) *PolicyInfo {
|
||||
return &PolicyInfo{
|
||||
Name: policyName,
|
||||
Resource: resource,
|
||||
Namespace: ns,
|
||||
success: true, // fail to be set explicity
|
||||
}
|
||||
}
|
||||
|
||||
//IsSuccessful checks if policy is succesful
|
||||
// the policy is set to fail, if any of the rules have failed
|
||||
func (pi *PolicyInfo) IsSuccessful() bool {
|
||||
return pi.success
|
||||
}
|
||||
|
||||
//ErrorRules returns error msgs from all rule
|
||||
func (pi *PolicyInfo) ErrorRules() string {
|
||||
errorMsgs := []string{}
|
||||
for _, r := range pi.rules {
|
||||
if !r.IsSuccessful() {
|
||||
errorMsgs = append(errorMsgs, r.Msgs...)
|
||||
}
|
||||
}
|
||||
return strings.Join(errorMsgs, ";")
|
||||
}
|
||||
|
||||
//RuleInfo defines rule struct
|
||||
type RuleInfo struct {
|
||||
Name string
|
||||
Msgs []string
|
||||
success bool
|
||||
}
|
||||
|
||||
//NewRuleInfo creates a new RuleInfo
|
||||
func NewRuleInfo(ruleName string) *RuleInfo {
|
||||
return &RuleInfo{
|
||||
Name: ruleName,
|
||||
Msgs: []string{},
|
||||
success: true, // fail to be set explicity
|
||||
}
|
||||
}
|
||||
|
||||
//Fail set the rule as failed
|
||||
func (ri *RuleInfo) Fail() {
|
||||
ri.success = false
|
||||
}
|
||||
|
||||
//IsSuccessful checks if rule is succesful
|
||||
func (ri *RuleInfo) IsSuccessful() bool {
|
||||
return ri.success
|
||||
}
|
||||
|
||||
//Add add msg
|
||||
func (ri *RuleInfo) Add(msg string) {
|
||||
ri.Msgs = append(ri.Msgs, msg)
|
||||
}
|
||||
|
||||
//Addf add msg with args
|
||||
func (ri *RuleInfo) Addf(msg string, args ...interface{}) {
|
||||
ri.Msgs = append(ri.Msgs, fmt.Sprintf(msg, args...))
|
||||
}
|
||||
|
||||
//RulesSuccesfuly check if the any rule has failed or not
|
||||
func RulesSuccesfuly(rules []*RuleInfo) bool {
|
||||
for _, r := range rules {
|
||||
if !r.success {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
//AddRuleInfos sets the rule information
|
||||
func (pi *PolicyInfo) AddRuleInfos(rules []*RuleInfo) {
|
||||
if !RulesSuccesfuly(rules) {
|
||||
pi.success = false
|
||||
}
|
||||
pi.rules = rules
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
@ -15,7 +16,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/info"
|
||||
"github.com/nirmata/kyverno/pkg/sharedinformer"
|
||||
tlsutils "github.com/nirmata/kyverno/pkg/tls"
|
||||
v1beta1 "k8s.io/api/admission/v1beta1"
|
||||
|
@ -94,7 +95,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
|||
|
||||
admissionReview.Response.UID = admissionReview.Request.UID
|
||||
|
||||
responseJson, err := json.Marshal(admissionReview)
|
||||
responseJSON, err := json.Marshal(admissionReview)
|
||||
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError)
|
||||
|
@ -102,7 +103,7 @@ func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
if _, err := w.Write(responseJson); err != nil {
|
||||
if _, err := w.Write(responseJSON); err != nil {
|
||||
http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
@ -133,43 +134,49 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
|
|||
|
||||
policies, err := ws.policyLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
// Unable to connect to policy Lister to access policies
|
||||
glog.Error("Unable to connect to policy controller to access policies. Mutation Rules are NOT being applied")
|
||||
glog.Warning(err)
|
||||
return nil
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
|
||||
admissionResult := result.NewAdmissionResult(string(request.UID))
|
||||
var allPatches []engine.PatchBytes
|
||||
policyInfos := []*info.PolicyInfo{}
|
||||
for _, policy := range policies {
|
||||
|
||||
// check if policy has a rule for the admission request kind
|
||||
if !StringInSlice(request.Kind.Kind, getApplicableKindsForPolicy(policy)) {
|
||||
continue
|
||||
}
|
||||
rname := engine.ParseNameFromObject(request.Object.Raw)
|
||||
rns := engine.ParseNamespaceFromObject(request.Object.Raw)
|
||||
policyInfo := info.NewPolicyInfo(policy.Name,
|
||||
rname,
|
||||
rns)
|
||||
|
||||
glog.V(3).Infof("Handling mutation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
|
||||
request.Kind.Kind, rns, rname, request.UID, request.Operation)
|
||||
|
||||
glog.Infof("Applying policy %s with %d rules\n", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||
|
||||
policyPatches, mutationResult := engine.Mutate(*policy, request.Object.Raw, request.Kind)
|
||||
policyPatches, ruleInfos := engine.Mutate(*policy, request.Object.Raw, request.Kind)
|
||||
policyInfo.AddRuleInfos(ruleInfos)
|
||||
if !policyInfo.IsSuccessful() {
|
||||
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
|
||||
for _, r := range ruleInfos {
|
||||
glog.Warning(r.Msgs)
|
||||
}
|
||||
} else if len(policyPatches) > 0 {
|
||||
allPatches = append(allPatches, policyPatches...)
|
||||
admissionResult = result.Append(admissionResult, mutationResult)
|
||||
|
||||
if mutationError := mutationResult.ToError(); mutationError != nil {
|
||||
glog.Warningf(mutationError.Error())
|
||||
glog.Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, rname, rns)
|
||||
}
|
||||
policyInfos = append(policyInfos, policyInfo)
|
||||
}
|
||||
|
||||
if len(policyPatches) > 0 {
|
||||
namespace := engine.ParseNamespaceFromObject(request.Object.Raw)
|
||||
name := engine.ParseNameFromObject(request.Object.Raw)
|
||||
glog.Infof("Mutation from policy %s has applied to %s %s/%s", policy.Name, request.Kind.Kind, namespace, name)
|
||||
}
|
||||
glog.Info(admissionResult.String())
|
||||
}
|
||||
|
||||
message := "\n" + admissionResult.String()
|
||||
|
||||
if admissionResult.GetReason() == result.Success {
|
||||
ok, msg := isAdmSuccesful(policyInfos)
|
||||
if ok {
|
||||
patchType := v1beta1.PatchTypeJSONPatch
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
|
@ -177,68 +184,155 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
|
|||
PatchType: &patchType,
|
||||
}
|
||||
}
|
||||
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: message,
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func isAdmSuccesful(policyInfos []*info.PolicyInfo) (bool, string) {
|
||||
var admSuccess = true
|
||||
var errMsgs []string
|
||||
for _, pi := range policyInfos {
|
||||
if !pi.IsSuccessful() {
|
||||
admSuccess = false
|
||||
errMsgs = append(errMsgs, fmt.Sprintf("\nPolicy %s failed with following rules", pi.Name))
|
||||
// Get the error rules
|
||||
errorRules := pi.ErrorRules()
|
||||
errMsgs = append(errMsgs, errorRules)
|
||||
}
|
||||
}
|
||||
return admSuccess, strings.Join(errMsgs, ";")
|
||||
}
|
||||
|
||||
// HandleValidation handles validating webhook admission request
|
||||
// If there are no errors in validating rule we apply generation rules
|
||||
func (ws *WebhookServer) HandleValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
||||
policyInfos := []*info.PolicyInfo{}
|
||||
|
||||
policies, err := ws.policyLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
// Unable to connect to policy Lister to access policies
|
||||
glog.Error("Unable to connect to policy controller to access policies. Validation Rules are NOT being applied")
|
||||
glog.Warning(err)
|
||||
return nil
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
|
||||
admissionResult := result.NewAdmissionResult(string(request.UID))
|
||||
for _, policy := range policies {
|
||||
|
||||
if !StringInSlice(request.Kind.Kind, getApplicableKindsForPolicy(policy)) {
|
||||
continue
|
||||
}
|
||||
rname := engine.ParseNameFromObject(request.Object.Raw)
|
||||
rns := engine.ParseNamespaceFromObject(request.Object.Raw)
|
||||
|
||||
policyInfo := info.NewPolicyInfo(policy.Name,
|
||||
rname,
|
||||
rns)
|
||||
|
||||
glog.V(3).Infof("Handling validation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation)
|
||||
request.Kind.Kind, rns, rname, request.UID, request.Operation)
|
||||
|
||||
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 validationError := validationResult.ToError(); validationError != nil {
|
||||
glog.Warningf(validationError.Error())
|
||||
ruleInfos, err := engine.Validate(*policy, request.Object.Raw, request.Kind)
|
||||
if err != nil {
|
||||
// This is not policy error
|
||||
// but if unable to parse request raw resource
|
||||
// TODO : create event ? dont think so
|
||||
glog.Error(err)
|
||||
continue
|
||||
}
|
||||
glog.Info(admissionResult.String())
|
||||
policyInfo.AddRuleInfos(ruleInfos)
|
||||
|
||||
if !policyInfo.IsSuccessful() {
|
||||
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
|
||||
for _, r := range ruleInfos {
|
||||
glog.Warning(r.Msgs)
|
||||
}
|
||||
} else {
|
||||
glog.Infof("Validation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, rname, rns)
|
||||
}
|
||||
policyInfos = append(policyInfos, policyInfo)
|
||||
}
|
||||
|
||||
message := "\n" + admissionResult.String()
|
||||
// If Validation fails then reject the request
|
||||
ok, msg := isAdmSuccesful(policyInfos)
|
||||
if !ok {
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
}
|
||||
// Process Generation
|
||||
return ws.HandleGeneration(request)
|
||||
}
|
||||
|
||||
// Generation loop after all validation succeeded
|
||||
var response *v1beta1.AdmissionResponse
|
||||
//HandleGeneration handles application of generation rules
|
||||
func (ws *WebhookServer) HandleGeneration(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
|
||||
if request.Kind.Kind != "Namespace" {
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
policyInfos := []*info.PolicyInfo{}
|
||||
|
||||
if admissionResult.GetReason() == result.Success {
|
||||
policies, err := ws.policyLister.List(labels.NewSelector())
|
||||
if err != nil {
|
||||
// Unable to connect to policy Lister to access policies
|
||||
glog.Error("Unable to connect to policy controller to access policies. Generation Rules are NOT being applied")
|
||||
glog.Warning(err)
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
for _, policy := range policies {
|
||||
engine.Generate(ws.client, *policy, request.Object.Raw, request.Kind)
|
||||
}
|
||||
glog.V(3).Info("Validation is successful")
|
||||
|
||||
response = &v1beta1.AdmissionResponse{
|
||||
if !StringInSlice(request.Kind.Kind, getApplicableKindsForPolicy(policy)) {
|
||||
continue
|
||||
}
|
||||
rname := engine.ParseNameFromObject(request.Object.Raw)
|
||||
rns := engine.ParseNamespaceFromObject(request.Object.Raw)
|
||||
|
||||
policyInfo := info.NewPolicyInfo(policy.Name,
|
||||
rname,
|
||||
rns)
|
||||
glog.V(3).Infof("Handling generation for Kind=%s, Namespace=%s Name=%s UID=%s patchOperation=%s",
|
||||
request.Kind.Kind, rns, rname, request.UID, request.Operation)
|
||||
glog.Infof("Applying policy %s with generation %d rules", policy.ObjectMeta.Name, len(policy.Spec.Rules))
|
||||
|
||||
ruleInfos := engine.Generate(ws.client, *policy, request.Object.Raw, request.Kind)
|
||||
policyInfo.AddRuleInfos(ruleInfos)
|
||||
if !policyInfo.IsSuccessful() {
|
||||
glog.Infof("Failed to apply policy %s on resource %s/%s", policy.Name, rname, rns)
|
||||
for _, r := range ruleInfos {
|
||||
glog.Warning(r.Msgs)
|
||||
}
|
||||
} else {
|
||||
glog.Infof("Generation from policy %s has succesfully applied to %s %s/%s", policy.Name, request.Kind.Kind, rns, rname)
|
||||
}
|
||||
policyInfos = append(policyInfos, policyInfo)
|
||||
}
|
||||
ok, msg := isAdmSuccesful(policyInfos)
|
||||
if ok {
|
||||
glog.V(3).Info("Generation is successful")
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
}
|
||||
} else {
|
||||
response = &v1beta1.AdmissionResponse{
|
||||
return &v1beta1.AdmissionResponse{
|
||||
Allowed: false,
|
||||
Result: &metav1.Status{
|
||||
Message: message,
|
||||
Message: msg,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// bodyToAdmissionReview creates AdmissionReview object from request body
|
||||
|
|
284
vendor/github.com/evanphx/json-patch/patch.go
generated
vendored
284
vendor/github.com/evanphx/json-patch/patch.go
generated
vendored
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -24,6 +26,14 @@ var (
|
|||
AccumulatedCopySizeLimit int64 = 0
|
||||
)
|
||||
|
||||
var (
|
||||
ErrTestFailed = errors.New("test failed")
|
||||
ErrMissing = errors.New("missing value")
|
||||
ErrUnknownType = errors.New("unknown object type")
|
||||
ErrInvalid = errors.New("invalid state detected")
|
||||
ErrInvalidIndex = errors.New("invalid index referenced")
|
||||
)
|
||||
|
||||
type lazyNode struct {
|
||||
raw *json.RawMessage
|
||||
doc partialDoc
|
||||
|
@ -31,10 +41,11 @@ type lazyNode struct {
|
|||
which int
|
||||
}
|
||||
|
||||
type operation map[string]*json.RawMessage
|
||||
// Operation is a single JSON-Patch step, such as a single 'add' operation.
|
||||
type Operation map[string]*json.RawMessage
|
||||
|
||||
// Patch is an ordered collection of operations.
|
||||
type Patch []operation
|
||||
// Patch is an ordered collection of Operations.
|
||||
type Patch []Operation
|
||||
|
||||
type partialDoc map[string]*lazyNode
|
||||
type partialArray []*lazyNode
|
||||
|
@ -59,7 +70,7 @@ func (n *lazyNode) MarshalJSON() ([]byte, error) {
|
|||
case eAry:
|
||||
return json.Marshal(n.ary)
|
||||
default:
|
||||
return nil, fmt.Errorf("Unknown type")
|
||||
return nil, ErrUnknownType
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +102,7 @@ func (n *lazyNode) intoDoc() (*partialDoc, error) {
|
|||
}
|
||||
|
||||
if n.raw == nil {
|
||||
return nil, fmt.Errorf("Unable to unmarshal nil pointer as partial document")
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
err := json.Unmarshal(*n.raw, &n.doc)
|
||||
|
@ -110,7 +121,7 @@ func (n *lazyNode) intoAry() (*partialArray, error) {
|
|||
}
|
||||
|
||||
if n.raw == nil {
|
||||
return nil, fmt.Errorf("Unable to unmarshal nil pointer as partial array")
|
||||
return nil, ErrInvalid
|
||||
}
|
||||
|
||||
err := json.Unmarshal(*n.raw, &n.ary)
|
||||
|
@ -227,7 +238,8 @@ func (n *lazyNode) equal(o *lazyNode) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
func (o operation) kind() string {
|
||||
// Kind reads the "op" field of the Operation.
|
||||
func (o Operation) Kind() string {
|
||||
if obj, ok := o["op"]; ok && obj != nil {
|
||||
var op string
|
||||
|
||||
|
@ -243,39 +255,41 @@ func (o operation) kind() string {
|
|||
return "unknown"
|
||||
}
|
||||
|
||||
func (o operation) path() string {
|
||||
// Path reads the "path" field of the Operation.
|
||||
func (o Operation) Path() (string, error) {
|
||||
if obj, ok := o["path"]; ok && obj != nil {
|
||||
var op string
|
||||
|
||||
err := json.Unmarshal(*obj, &op)
|
||||
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
return "unknown", err
|
||||
}
|
||||
|
||||
return op
|
||||
return op, nil
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
return "unknown", errors.Wrapf(ErrMissing, "operation missing path field")
|
||||
}
|
||||
|
||||
func (o operation) from() string {
|
||||
// From reads the "from" field of the Operation.
|
||||
func (o Operation) From() (string, error) {
|
||||
if obj, ok := o["from"]; ok && obj != nil {
|
||||
var op string
|
||||
|
||||
err := json.Unmarshal(*obj, &op)
|
||||
|
||||
if err != nil {
|
||||
return "unknown"
|
||||
return "unknown", err
|
||||
}
|
||||
|
||||
return op
|
||||
return op, nil
|
||||
}
|
||||
|
||||
return "unknown"
|
||||
return "unknown", errors.Wrapf(ErrMissing, "operation, missing from field")
|
||||
}
|
||||
|
||||
func (o operation) value() *lazyNode {
|
||||
func (o Operation) value() *lazyNode {
|
||||
if obj, ok := o["value"]; ok {
|
||||
return newLazyNode(obj)
|
||||
}
|
||||
|
@ -283,6 +297,23 @@ func (o operation) value() *lazyNode {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ValueInterface decodes the operation value into an interface.
|
||||
func (o Operation) ValueInterface() (interface{}, error) {
|
||||
if obj, ok := o["value"]; ok && obj != nil {
|
||||
var v interface{}
|
||||
|
||||
err := json.Unmarshal(*obj, &v)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return nil, errors.Wrapf(ErrMissing, "operation, missing value field")
|
||||
}
|
||||
|
||||
func isArray(buf []byte) bool {
|
||||
Loop:
|
||||
for _, c := range buf {
|
||||
|
@ -359,7 +390,7 @@ func (d *partialDoc) get(key string) (*lazyNode, error) {
|
|||
func (d *partialDoc) remove(key string) error {
|
||||
_, ok := (*d)[key]
|
||||
if !ok {
|
||||
return fmt.Errorf("Unable to remove nonexistent key: %s", key)
|
||||
return errors.Wrapf(ErrMissing, "Unable to remove nonexistent key: %s", key)
|
||||
}
|
||||
|
||||
delete(*d, key)
|
||||
|
@ -385,7 +416,7 @@ func (d *partialArray) add(key string, val *lazyNode) error {
|
|||
|
||||
idx, err := strconv.Atoi(key)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "value was not a proper array index: '%s'", key)
|
||||
}
|
||||
|
||||
sz := len(*d) + 1
|
||||
|
@ -395,12 +426,12 @@ func (d *partialArray) add(key string, val *lazyNode) error {
|
|||
cur := *d
|
||||
|
||||
if idx >= len(ary) {
|
||||
return fmt.Errorf("Unable to access invalid index: %d", idx)
|
||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
||||
}
|
||||
|
||||
if SupportNegativeIndices {
|
||||
if idx < -len(ary) {
|
||||
return fmt.Errorf("Unable to access invalid index: %d", idx)
|
||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
||||
}
|
||||
|
||||
if idx < 0 {
|
||||
|
@ -424,7 +455,7 @@ func (d *partialArray) get(key string) (*lazyNode, error) {
|
|||
}
|
||||
|
||||
if idx >= len(*d) {
|
||||
return nil, fmt.Errorf("Unable to access invalid index: %d", idx)
|
||||
return nil, errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
||||
}
|
||||
|
||||
return (*d)[idx], nil
|
||||
|
@ -439,12 +470,12 @@ func (d *partialArray) remove(key string) error {
|
|||
cur := *d
|
||||
|
||||
if idx >= len(cur) {
|
||||
return fmt.Errorf("Unable to access invalid index: %d", idx)
|
||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
||||
}
|
||||
|
||||
if SupportNegativeIndices {
|
||||
if idx < -len(cur) {
|
||||
return fmt.Errorf("Unable to access invalid index: %d", idx)
|
||||
return errors.Wrapf(ErrInvalidIndex, "Unable to access invalid index: %d", idx)
|
||||
}
|
||||
|
||||
if idx < 0 {
|
||||
|
@ -462,140 +493,189 @@ func (d *partialArray) remove(key string) error {
|
|||
|
||||
}
|
||||
|
||||
func (p Patch) add(doc *container, op operation) error {
|
||||
path := op.path()
|
||||
|
||||
con, key := findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch add operation does not apply: doc is missing path: \"%s\"", path)
|
||||
}
|
||||
|
||||
return con.add(key, op.value())
|
||||
}
|
||||
|
||||
func (p Patch) remove(doc *container, op operation) error {
|
||||
path := op.path()
|
||||
|
||||
con, key := findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch remove operation does not apply: doc is missing path: \"%s\"", path)
|
||||
}
|
||||
|
||||
return con.remove(key)
|
||||
}
|
||||
|
||||
func (p Patch) replace(doc *container, op operation) error {
|
||||
path := op.path()
|
||||
|
||||
con, key := findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing path: %s", path)
|
||||
}
|
||||
|
||||
_, ok := con.get(key)
|
||||
if ok != nil {
|
||||
return fmt.Errorf("jsonpatch replace operation does not apply: doc is missing key: %s", path)
|
||||
}
|
||||
|
||||
return con.set(key, op.value())
|
||||
}
|
||||
|
||||
func (p Patch) move(doc *container, op operation) error {
|
||||
from := op.from()
|
||||
|
||||
con, key := findObject(doc, from)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch move operation does not apply: doc is missing from path: %s", from)
|
||||
}
|
||||
|
||||
val, err := con.get(key)
|
||||
func (p Patch) add(doc *container, op Operation) error {
|
||||
path, err := op.Path()
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(ErrMissing, "add operation failed to decode path")
|
||||
}
|
||||
|
||||
con, key := findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return errors.Wrapf(ErrMissing, "add operation does not apply: doc is missing path: \"%s\"", path)
|
||||
}
|
||||
|
||||
err = con.add(key, op.value())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error in add for path: '%s'", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Patch) remove(doc *container, op Operation) error {
|
||||
path, err := op.Path()
|
||||
if err != nil {
|
||||
return errors.Wrapf(ErrMissing, "remove operation failed to decode path")
|
||||
}
|
||||
|
||||
con, key := findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return errors.Wrapf(ErrMissing, "remove operation does not apply: doc is missing path: \"%s\"", path)
|
||||
}
|
||||
|
||||
err = con.remove(key)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "error in remove for path: '%s'", path)
|
||||
}
|
||||
|
||||
path := op.path()
|
||||
|
||||
con, key = findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch move operation does not apply: doc is missing destination path: %s", path)
|
||||
return nil
|
||||
}
|
||||
|
||||
return con.add(key, val)
|
||||
func (p Patch) replace(doc *container, op Operation) error {
|
||||
path, err := op.Path()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "replace operation failed to decode path")
|
||||
}
|
||||
|
||||
func (p Patch) test(doc *container, op operation) error {
|
||||
path := op.path()
|
||||
|
||||
con, key := findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch test operation does not apply: is missing path: %s", path)
|
||||
return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing path: %s", path)
|
||||
}
|
||||
|
||||
_, ok := con.get(key)
|
||||
if ok != nil {
|
||||
return errors.Wrapf(ErrMissing, "replace operation does not apply: doc is missing key: %s", path)
|
||||
}
|
||||
|
||||
err = con.set(key, op.value())
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error in remove for path: '%s'", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Patch) move(doc *container, op Operation) error {
|
||||
from, err := op.From()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "move operation failed to decode from")
|
||||
}
|
||||
|
||||
con, key := findObject(doc, from)
|
||||
|
||||
if con == nil {
|
||||
return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing from path: %s", from)
|
||||
}
|
||||
|
||||
val, err := con.get(key)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "error in move for path: '%s'", key)
|
||||
}
|
||||
|
||||
err = con.remove(key)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error in move for path: '%s'", key)
|
||||
}
|
||||
|
||||
path, err := op.Path()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "move operation failed to decode path")
|
||||
}
|
||||
|
||||
con, key = findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return errors.Wrapf(ErrMissing, "move operation does not apply: doc is missing destination path: %s", path)
|
||||
}
|
||||
|
||||
err = con.add(key, val)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error in move for path: '%s'", path)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p Patch) test(doc *container, op Operation) error {
|
||||
path, err := op.Path()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "test operation failed to decode path")
|
||||
}
|
||||
|
||||
con, key := findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return errors.Wrapf(ErrMissing, "test operation does not apply: is missing path: %s", path)
|
||||
}
|
||||
|
||||
val, err := con.get(key)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error in test for path: '%s'", path)
|
||||
}
|
||||
|
||||
if val == nil {
|
||||
if op.value().raw == nil {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Testing value %s failed", path)
|
||||
return errors.Wrapf(ErrTestFailed, "testing value %s failed", path)
|
||||
} else if op.value() == nil {
|
||||
return fmt.Errorf("Testing value %s failed", path)
|
||||
return errors.Wrapf(ErrTestFailed, "testing value %s failed", path)
|
||||
}
|
||||
|
||||
if val.equal(op.value()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("Testing value %s failed", path)
|
||||
return errors.Wrapf(ErrTestFailed, "testing value %s failed", path)
|
||||
}
|
||||
|
||||
func (p Patch) copy(doc *container, op operation, accumulatedCopySize *int64) error {
|
||||
from := op.from()
|
||||
func (p Patch) copy(doc *container, op Operation, accumulatedCopySize *int64) error {
|
||||
from, err := op.From()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "copy operation failed to decode from")
|
||||
}
|
||||
|
||||
con, key := findObject(doc, from)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing from path: %s", from)
|
||||
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing from path: %s", from)
|
||||
}
|
||||
|
||||
val, err := con.get(key)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "error in copy for from: '%s'", from)
|
||||
}
|
||||
|
||||
path := op.path()
|
||||
path, err := op.Path()
|
||||
if err != nil {
|
||||
return errors.Wrapf(ErrMissing, "copy operation failed to decode path")
|
||||
}
|
||||
|
||||
con, key = findObject(doc, path)
|
||||
|
||||
if con == nil {
|
||||
return fmt.Errorf("jsonpatch copy operation does not apply: doc is missing destination path: %s", path)
|
||||
return errors.Wrapf(ErrMissing, "copy operation does not apply: doc is missing destination path: %s", path)
|
||||
}
|
||||
|
||||
valCopy, sz, err := deepCopy(val)
|
||||
if err != nil {
|
||||
return err
|
||||
return errors.Wrapf(err, "error while performing deep copy")
|
||||
}
|
||||
|
||||
(*accumulatedCopySize) += int64(sz)
|
||||
if AccumulatedCopySizeLimit > 0 && *accumulatedCopySize > AccumulatedCopySizeLimit {
|
||||
return NewAccumulatedCopySizeError(AccumulatedCopySizeLimit, *accumulatedCopySize)
|
||||
}
|
||||
|
||||
return con.add(key, valCopy)
|
||||
err = con.add(key, valCopy)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error while adding value during copy")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Equal indicates if 2 JSON documents have the same structural equality.
|
||||
|
@ -651,7 +731,7 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
|
|||
var accumulatedCopySize int64
|
||||
|
||||
for _, op := range p {
|
||||
switch op.kind() {
|
||||
switch op.Kind() {
|
||||
case "add":
|
||||
err = p.add(&pd, op)
|
||||
case "remove":
|
||||
|
@ -665,7 +745,7 @@ func (p Patch) ApplyIndent(doc []byte, indent string) ([]byte, error) {
|
|||
case "copy":
|
||||
err = p.copy(&pd, op, &accumulatedCopySize)
|
||||
default:
|
||||
err = fmt.Errorf("Unexpected kind: %s", op.kind())
|
||||
err = fmt.Errorf("Unexpected kind: %s", op.Kind())
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
|
|
Loading…
Reference in a new issue