1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-06 16:06:56 +00:00
kyverno/pkg/engine/overlay.go

330 lines
8.4 KiB
Go
Raw Normal View History

2019-05-21 18:27:56 +03:00
package engine
import (
"encoding/json"
"fmt"
2019-05-22 18:28:38 +01:00
"reflect"
"strconv"
jsonpatch "github.com/evanphx/json-patch"
2019-06-05 16:44:53 +03:00
kubepolicy "github.com/nirmata/kyverno/pkg/apis/policy/v1alpha1"
"github.com/nirmata/kyverno/pkg/result"
2019-05-21 18:27:56 +03:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ProcessOverlay handles validating admission request
// Checks the target resourse for rules defined in the policy
2019-06-05 16:44:53 +03:00
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
}
2019-05-21 18:27:56 +03:00
var resource interface{}
var appliedPatches []PatchBytes
json.Unmarshal(rawResource, &resource)
2019-06-05 16:44:53 +03:00
patch := applyOverlay(resource, *rule.Mutation.Overlay, "/", &overlayApplicationResult)
if overlayApplicationResult.GetReason() == result.Success {
appliedPatches = append(appliedPatches, patch...)
2019-05-21 18:27:56 +03:00
}
return appliedPatches, overlayApplicationResult
2019-05-21 18:27:56 +03:00
}
// goes down through overlay and resource trees and applies overlay
func applyOverlay(resource, overlay interface{}, path string, res *result.RuleApplicationResult) []PatchBytes {
var appliedPatches []PatchBytes
2019-05-22 18:28:38 +01:00
// resource item exists but has different type - replace
// all subtree within this path by overlay
if reflect.TypeOf(resource) != reflect.TypeOf(overlay) {
patch := replaceSubtree(overlay, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
return appliedPatches
2019-05-22 18:28:38 +01:00
}
2019-05-21 18:27:56 +03:00
switch typedOverlay := overlay.(type) {
case map[string]interface{}:
typedResource := resource.(map[string]interface{})
for key, value := range typedOverlay {
if wrappedWithParentheses(key) {
2019-05-27 18:11:39 +03:00
continue
}
currentPath := path + key + "/"
2019-05-21 18:27:56 +03:00
resourcePart, ok := typedResource[key]
if ok {
patches := applyOverlay(resourcePart, value, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
2019-05-21 18:27:56 +03:00
} else {
patch := insertSubtree(value, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
appliedPatches = append(appliedPatches, patch)
2019-05-21 18:27:56 +03:00
}
}
case []interface{}:
typedResource := resource.([]interface{})
patches := applyOverlayToArray(typedResource, typedOverlay, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
2019-05-27 18:21:23 +03:00
case string, float64, int64, bool:
patch := replaceSubtree(overlay, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
default:
res.FailWithMessagef("Overlay has unsupported type: %T", overlay)
return nil
2019-05-21 18:27:56 +03:00
}
return appliedPatches
2019-05-21 18:27:56 +03:00
}
// for each overlay and resource array elements and applies overlay
func applyOverlayToArray(resource, overlay []interface{}, path string, res *result.RuleApplicationResult) []PatchBytes {
var appliedPatches []PatchBytes
if len(overlay) == 0 {
res.FailWithMessagef("Empty array detected in the overlay")
return nil
}
if len(resource) == 0 {
return fillEmptyArray(overlay, path, res)
}
if reflect.TypeOf(resource[0]) != reflect.TypeOf(overlay[0]) {
res.FailWithMessagef("overlay array and resource array have elements of different types: %T and %T", overlay[0], resource[0])
return nil
}
2019-05-21 18:27:56 +03:00
switch overlay[0].(type) {
case map[string]interface{}:
for _, overlayElement := range overlay {
typedOverlay := overlayElement.(map[string]interface{})
anchors := GetAnchorsFromMap(typedOverlay)
if len(anchors) > 0 {
for i, resourceElement := range resource {
typedResource := resourceElement.(map[string]interface{})
2019-05-21 18:27:56 +03:00
2019-05-23 12:51:45 +03:00
currentPath := path + strconv.Itoa(i) + "/"
2019-05-21 18:27:56 +03:00
if !skipArrayObject(typedResource, anchors) {
patches := applyOverlay(resourceElement, overlayElement, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
}
}
}
} else if hasNestedAnchors(overlayElement) {
for i, resourceElement := range resource {
currentPath := path + strconv.Itoa(i) + "/"
patches := applyOverlay(resourceElement, overlayElement, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patches...)
2019-05-21 18:27:56 +03:00
}
}
} else {
currentPath := path + "0/"
patch := insertSubtree(overlayElement, currentPath, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
2019-05-21 18:27:56 +03:00
}
}
}
default:
path += "0/"
2019-05-21 18:27:56 +03:00
for _, value := range overlay {
patch := insertSubtree(value, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
2019-05-21 18:27:56 +03:00
}
}
return appliedPatches
}
// In case of empty resource array
// append all non-anchor items to front
func fillEmptyArray(overlay []interface{}, path string, res *result.RuleApplicationResult) []PatchBytes {
var appliedPatches []PatchBytes
if len(overlay) == 0 {
res.FailWithMessagef("Empty array detected in the overlay")
return nil
}
path += "0/"
switch overlay[0].(type) {
case map[string]interface{}:
for _, overlayElement := range overlay {
typedOverlay := overlayElement.(map[string]interface{})
anchors := GetAnchorsFromMap(typedOverlay)
if len(anchors) == 0 {
patch := insertSubtree(overlayElement, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
}
}
default:
for _, overlayElement := range overlay {
patch := insertSubtree(overlayElement, path, res)
if res.Reason == result.Success {
appliedPatches = append(appliedPatches, patch)
}
2019-05-22 18:28:38 +01:00
}
2019-05-21 18:27:56 +03:00
}
return appliedPatches
2019-05-21 18:27:56 +03:00
}
func insertSubtree(overlay interface{}, path string, res *result.RuleApplicationResult) []byte {
return processSubtree(overlay, path, "add", res)
}
func replaceSubtree(overlay interface{}, path string, res *result.RuleApplicationResult) []byte {
return processSubtree(overlay, path, "replace", res)
}
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]
}
if path == "" {
path = "/"
}
value := prepareJSONValue(overlay)
patchStr := fmt.Sprintf(`{ "op": "%s", "path": "%s", "value": %s }`, op, path, value)
2019-05-21 18:27:56 +03:00
// check the patch
_, err := jsonpatch.DecodePatch([]byte("[" + patchStr + "]"))
if err != nil {
res.FailWithMessagef("Failed to make '%s' patch from an overlay '%s' for path %s", op, value, path)
return nil
}
return PatchBytes(patchStr)
2019-05-21 18:27:56 +03:00
}
// TODO: Overlay is already in JSON, remove this code
// converts overlay to JSON string to be inserted into the JSON Patch
func prepareJSONValue(overlay interface{}) string {
switch typed := overlay.(type) {
case map[string]interface{}:
if len(typed) == 0 {
return ""
}
if hasOnlyAnchors(overlay) {
return ""
}
result := ""
for key, value := range typed {
jsonValue := prepareJSONValue(value)
pair := fmt.Sprintf(`"%s":%s`, key, jsonValue)
if result != "" {
result += ", "
}
result += pair
}
result = fmt.Sprintf(`{ %s }`, result)
return result
case []interface{}:
if len(typed) == 0 {
return ""
}
2019-05-21 18:27:56 +03:00
if hasOnlyAnchors(overlay) {
return ""
}
result := ""
for _, value := range typed {
jsonValue := prepareJSONValue(value)
if result != "" {
result += ", "
}
result += jsonValue
}
result = fmt.Sprintf(`[ %s ]`, result)
return result
case string:
return fmt.Sprintf(`"%s"`, typed)
case float64:
return fmt.Sprintf("%f", typed)
case int64:
return fmt.Sprintf("%d", typed)
2019-05-27 18:21:23 +03:00
case bool:
return fmt.Sprintf("%t", typed)
default:
return ""
}
}
func hasOnlyAnchors(overlay interface{}) bool {
switch typed := overlay.(type) {
case map[string]interface{}:
if anchors := GetAnchorsFromMap(typed); len(anchors) == len(typed) {
return true
}
for _, value := range typed {
if !hasOnlyAnchors(value) {
return false
}
}
return true
default:
return false
}
}
func hasNestedAnchors(overlay interface{}) bool {
switch typed := overlay.(type) {
case map[string]interface{}:
if anchors := GetAnchorsFromMap(typed); len(anchors) > 0 {
return true
}
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
2019-05-23 14:51:41 +03:00
return false
case []interface{}:
for _, value := range typed {
if hasNestedAnchors(value) {
return true
}
}
return false
default:
return false
}
2019-05-21 18:27:56 +03:00
}