1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

refactor: add a json patch util and use it in autogen package (#3524)

Signed-off-by: Charles-Edouard Brétéché <charled.breteche@gmail.com>
This commit is contained in:
Charles-Edouard Brétéché 2022-04-01 07:26:47 +02:00 committed by GitHub
parent adca5f200b
commit 663ad49dca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 132 additions and 293 deletions

View file

@ -6,11 +6,11 @@ import (
"strconv"
"strings"
jsonpatch "github.com/evanphx/json-patch"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/toggle"
"github.com/kyverno/kyverno/pkg/utils"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
log "sigs.k8s.io/controller-runtime/pkg/log"
)
@ -168,55 +168,37 @@ func GetControllers(meta *metav1.ObjectMeta, spec *kyverno.Spec, log logr.Logger
// GenerateRulePatches generates rule for podControllers based on scenario A and C
func GenerateRulePatches(spec *kyverno.Spec, controllers string, log logr.Logger) (rulePatches [][]byte, errs []error) {
rules := spec.Rules
insertIdx := len(rules)
ruleMap := createRuleMap(rules)
ruleMap := createRuleMap(spec.Rules)
var ruleIndex = make(map[string]int)
for index, rule := range rules {
for index, rule := range spec.Rules {
ruleIndex[rule.Name] = index
}
for _, rule := range rules {
insertIdx := len(spec.Rules)
for _, rule := range spec.Rules {
patchPostion := insertIdx
convertToPatches := func(genRule kyvernoRule, patchPostion int) []byte {
operation := "add"
if existingAutoGenRule, alreadyExists := ruleMap[genRule.Name]; alreadyExists {
existingAutoGenRuleRaw, _ := json.Marshal(existingAutoGenRule)
genRuleRaw, _ := json.Marshal(genRule)
if string(existingAutoGenRuleRaw) == string(genRuleRaw) {
return nil
}
operation = "replace"
patchPostion = ruleIndex[genRule.Name]
}
// generate patch bytes
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value interface{} `json:"value"`
}{
fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)),
operation,
genRule,
}
pbytes, err := json.Marshal(jsonPatch)
patch := jsonutils.NewPatch(fmt.Sprintf("/spec/rules/%s", strconv.Itoa(patchPostion)), operation, genRule)
pbytes, err := patch.Marshal()
if err != nil {
errs = append(errs, err)
return nil
}
// check the patch
if _, err := jsonpatch.DecodePatch([]byte("[" + string(pbytes) + "]")); err != nil {
if err := jsonutils.CheckPatch(pbytes); err != nil {
errs = append(errs, err)
return nil
}
return pbytes
}
// handle all other controllers other than CronJob
genRule := generateRuleForControllers(rule, stripCronJob(controllers), log)
if genRule != nil {
@ -228,7 +210,6 @@ func GenerateRulePatches(spec *kyverno.Spec, controllers string, log logr.Logger
insertIdx++
patchPostion = insertIdx
}
// handle CronJob, it appends an additional rule
genRule = generateCronJobRule(rule, controllers, log)
if genRule != nil {
@ -312,14 +293,11 @@ func ComputeRules(p kyverno.PolicyInterface) []kyverno.Rule {
return spec.Rules
}
applyAutoGen, desiredControllers := CanAutoGen(spec, log.Log)
if !applyAutoGen {
desiredControllers = "none"
}
ann := p.GetAnnotations()
actualControllers, ok := ann[kyverno.PodControllersAnnotation]
if !ok || !applyAutoGen {
actualControllers = desiredControllers
} else {
@ -327,7 +305,6 @@ func ComputeRules(p kyverno.PolicyInterface) []kyverno.Rule {
actualControllers = desiredControllers
}
}
if actualControllers == "none" {
return spec.Rules
}

View file

@ -271,7 +271,7 @@ func Test_Any(t *testing.T) {
}
rulePatches, errs := GenerateRulePatches(spec, PodControllers, log.Log)
fmt.Println("utils.JoinPatches(patches)erterter", string(jsonutils.JoinPatches(rulePatches)))
fmt.Println("utils.JoinPatches(patches)erterter", string(jsonutils.JoinPatches(rulePatches...)))
if len(errs) != 0 {
t.Log(errs)
}
@ -545,7 +545,7 @@ func Test_Deny(t *testing.T) {
}
rulePatches, errs := GenerateRulePatches(spec, PodControllers, log.Log)
fmt.Println("utils.JoinPatches(patches)erterter", string(jsonutils.JoinPatches(rulePatches)))
fmt.Println("utils.JoinPatches(patches)erterter", string(jsonutils.JoinPatches(rulePatches...)))
if len(errs) != 0 {
t.Log(errs)
}

View file

@ -5,9 +5,9 @@ import (
"strconv"
"strings"
commonAnchor "github.com/kyverno/kyverno/pkg/engine/anchor"
jsonpatch "github.com/evanphx/json-patch/v5"
commonAnchor "github.com/kyverno/kyverno/pkg/engine/anchor"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"sigs.k8s.io/controller-runtime/pkg/log"
)
@ -41,7 +41,7 @@ func ApplyPatches(resource []byte, patches [][]byte) ([]byte, error) {
if len(patches) == 0 {
return resource, nil
}
joinedPatches := JoinPatches(patches)
joinedPatches := jsonutils.JoinPatches(patches...)
patch, err := jsonpatch.DecodePatch(joinedPatches)
if err != nil {
log.Log.V(4).Info("failed to decode JSON patch", "patch", patch)
@ -74,25 +74,6 @@ func ApplyPatchNew(resource, patch []byte) ([]byte, error) {
}
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
func JoinPatches(patches [][]byte) []byte {
var result []byte
if len(patches) == 0 {
return result
}
result = append(result, []byte("[\n")...)
for index, patch := range patches {
result = append(result, patch...)
if index != len(patches)-1 {
result = append(result, []byte(",\n")...)
}
}
result = append(result, []byte("\n]")...)
return result
}
//ConvertToUnstructured converts the resource to unstructured format
func ConvertToUnstructured(data []byte) (*unstructured.Unstructured, error) {
resource := &unstructured.Unstructured{}

View file

@ -3,6 +3,7 @@ package generate
import (
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
"k8s.io/apimachinery/pkg/api/errors"
"sigs.k8s.io/controller-runtime/pkg/log"
)
@ -21,72 +22,60 @@ type StatusControl struct {
//Failed sets gr status.state to failed with message
func (sc StatusControl) Failed(gr kyverno.GenerateRequest, message string, genResources []kyverno.ResourceSpec) error {
patch := []PatchOp{
{
Op: "replace",
Path: "/status",
Value: &kyverno.GenerateRequestStatus{
State: kyverno.Failed,
Message: message,
GeneratedResources: genResources, // Update Generated Resources
},
patch := jsonutils.NewPatch(
"/status",
"replace",
&kyverno.GenerateRequestStatus{
State: kyverno.Failed,
Message: message,
GeneratedResources: genResources, // Update Generated Resources
},
}
)
_, err := PatchGenerateRequest(&gr, patch, sc.client, "status")
if err != nil && !errors.IsNotFound(err) {
log.Log.Error(err, "failed to patch generate request status", "name", gr.Name)
return err
}
log.Log.V(3).Info("updated generate request status", "name", gr.Name, "status", string(kyverno.Failed))
return nil
}
// Success sets the gr status.state to completed and clears message
func (sc StatusControl) Success(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error {
patch := []PatchOp{
{
Op: "replace",
Path: "/status",
Value: &kyverno.GenerateRequestStatus{
State: kyverno.Completed,
Message: "",
GeneratedResources: genResources, // Update Generated Resources
},
patch := jsonutils.NewPatch(
"/status",
"replace",
&kyverno.GenerateRequestStatus{
State: kyverno.Completed,
Message: "",
GeneratedResources: genResources, // Update Generated Resources
},
}
)
_, err := PatchGenerateRequest(&gr, patch, sc.client, "status")
if err != nil && !errors.IsNotFound(err) {
log.Log.Error(err, "failed to patch generate request status", "name", gr.Name)
return err
}
log.Log.V(3).Info("updated generate request status", "name", gr.Name, "status", string(kyverno.Completed))
return nil
}
// Success sets the gr status.state to completed and clears message
func (sc StatusControl) Skip(gr kyverno.GenerateRequest, genResources []kyverno.ResourceSpec) error {
patch := []PatchOp{
{
Op: "replace",
Path: "/status",
Value: &kyverno.GenerateRequestStatus{
State: kyverno.Skip,
Message: "",
GeneratedResources: genResources, // Update Generated Resources
},
patch := jsonutils.NewPatch(
"/status",
"replace",
&kyverno.GenerateRequestStatus{
State: kyverno.Skip,
Message: "",
GeneratedResources: genResources, // Update Generated Resources
},
}
)
_, err := PatchGenerateRequest(&gr, patch, sc.client, "status")
if err != nil && !errors.IsNotFound(err) {
log.Log.Error(err, "failed to patch generate request status", "name", gr.Name)
return err
}
log.Log.V(3).Info("updated generate request status", "name", gr.Name, "status", string(kyverno.Skip))
return nil
}

View file

@ -2,33 +2,24 @@ package generate
import (
"context"
"encoding/json"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
kyvernoclient "github.com/kyverno/kyverno/pkg/client/clientset/versioned"
"github.com/kyverno/kyverno/pkg/config"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
// PatchOp represents a json patch operation
type PatchOp struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value,omitempty"`
}
// PatchGenerateRequest patches a generate request object
func PatchGenerateRequest(gr *kyverno.GenerateRequest, patch []PatchOp, client kyvernoclient.Interface, subresources ...string) (*kyverno.GenerateRequest, error) {
data, err := json.Marshal(patch)
func PatchGenerateRequest(gr *kyverno.GenerateRequest, patch jsonutils.Patch, client kyvernoclient.Interface, subresources ...string) (*kyverno.GenerateRequest, error) {
data, err := patch.ToPatchBytes()
if nil != err {
return gr, err
}
newGR, err := client.KyvernoV1().GenerateRequests(config.KyvernoNamespace).Patch(context.TODO(), gr.Name, types.JSONPatchType, data, metav1.PatchOptions{}, subresources...)
if err != nil {
return gr, err
}
return newGR, nil
}

View file

@ -180,24 +180,10 @@ func MutatePolicy(policy v1.PolicyInterface, logger logr.Logger) (v1.PolicyInter
if len(patches) == 0 {
return policy, nil
}
type jsonPatch struct {
Path string `json:"path"`
Op string `json:"op"`
Value interface{} `json:"value"`
}
var jsonPatches []jsonPatch
err := json.Unmarshal(patches, &jsonPatches)
if err != nil {
return nil, sanitizederror.NewWithError(fmt.Sprintf("failed to unmarshal patches for %s policy", policy.GetName()), err)
}
patch, err := jsonpatch.DecodePatch(patches)
if err != nil {
return nil, sanitizederror.NewWithError(fmt.Sprintf("failed to decode patch for %s policy", policy.GetName()), err)
}
policyBytes, _ := json.Marshal(policy)
if err != nil {
return nil, sanitizederror.NewWithError(fmt.Sprintf("failed to marshal %s policy", policy.GetName()), err)
@ -206,7 +192,6 @@ func MutatePolicy(policy v1.PolicyInterface, logger logr.Logger) (v1.PolicyInter
if err != nil {
return nil, sanitizederror.NewWithError(fmt.Sprintf("failed to apply %s policy", policy.GetName()), err)
}
var p v1.ClusterPolicy
err = json.Unmarshal(modifiedPolicy, &p)
if err != nil {

View file

@ -1,7 +1,6 @@
package policy
import (
"encoding/json"
"fmt"
"reflect"
"strings"
@ -110,7 +109,7 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
patches := rule.Patches
patch, err := jsonpatch.DecodePatch(jsonutils.JoinPatches(patches))
patch, err := jsonpatch.DecodePatch(jsonutils.JoinPatches(patches...))
if err != nil {
log.Error(err, "failed to decode JSON patch", "patches", patches)
return &response.EngineResponse{}, err
@ -133,23 +132,16 @@ func getFailedOverallRuleInfo(resource unstructured.Unstructured, engineResponse
return engineResponse, nil
}
type jsonPatch struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}
func extractPatchPath(patches [][]byte, log logr.Logger) string {
var resultPath []string
// extract the patch path and value
for _, patch := range patches {
log.V(4).Info("expected json patch not found in resource", "patch", string(patch))
var data jsonPatch
if err := json.Unmarshal(patch, &data); err != nil {
if data, err := jsonutils.UnmarshalPatch(patch); err != nil {
log.Error(err, "failed to decode the generate patch", "patch", string(patch))
continue
} else {
resultPath = append(resultPath, data.Path)
}
resultPath = append(resultPath, data.Path)
}
return strings.Join(resultPath, ";")
}

View file

@ -1,7 +1,6 @@
package policymutation
import (
"encoding/json"
"fmt"
"strconv"
"strings"
@ -27,18 +26,15 @@ func GenerateJSONPatchesForDefaults(policy kyverno.PolicyInterface, log logr.Log
patches = append(patches, patch)
updateMsgs = append(updateMsgs, updateMsg)
}
// default 'Background'
if patch, updateMsg := defaultBackgroundFlag(spec, log); patch != nil {
patches = append(patches, patch)
updateMsgs = append(updateMsgs, updateMsg)
}
if patch, updateMsg := defaultFailurePolicy(spec, log); patch != nil {
patches = append(patches, patch)
updateMsgs = append(updateMsgs, updateMsg)
}
// if autogenInternals is enabled, we don't mutate rules in the webhook
if !toggle.AutogenInternals() {
patch, errs := GeneratePodControllerRule(policy, log)
@ -52,7 +48,6 @@ func GenerateJSONPatchesForDefaults(policy kyverno.PolicyInterface, log logr.Log
}
patches = append(patches, patch...)
}
formatedGVK, errs := checkForGVKFormatPatch(policy, log)
if len(errs) > 0 {
var errMsgs []string
@ -63,8 +58,7 @@ func GenerateJSONPatchesForDefaults(policy kyverno.PolicyInterface, log logr.Log
updateMsgs = append(updateMsgs, strings.Join(errMsgs, ";"))
}
patches = append(patches, formatedGVK...)
return jsonutils.JoinPatches(patches), updateMsgs
return jsonutils.JoinPatches(patches...), updateMsgs
}
func checkForGVKFormatPatch(policy kyverno.PolicyInterface, log logr.Logger) (patches [][]byte, errs []error) {
@ -133,112 +127,59 @@ func convertGVKForKinds(path string, kinds []string, log logr.Logger) ([]byte, e
}
kindList = append(kindList, gvk)
}
if len(kindList) == 0 {
return nil, nil
}
p, err := buildReplaceJsonPatch(path, kindList)
p, err := jsonutils.MarshalPatch(path, "replace", kindList)
log.V(4).WithName("convertGVKForKinds").Info("generated patch", "patch", string(p))
return p, err
}
func buildReplaceJsonPatch(path string, kindList []string) ([]byte, error) {
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value []string `json:"value"`
}{
path,
"replace",
kindList,
}
return json.Marshal(jsonPatch)
}
func defaultBackgroundFlag(spec *kyverno.Spec, log logr.Logger) ([]byte, string) {
// set 'Background' flag to 'true' if not specified
defaultVal := true
if spec.Background == nil {
defaultVal := true
log.V(4).Info("setting default value", "spec.background", true)
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value *bool `json:"value"`
}{
"/spec/background",
"add",
&defaultVal,
}
patchByte, err := json.Marshal(jsonPatch)
patchByte, err := jsonutils.MarshalPatch("/spec/background", "add", &defaultVal)
if err != nil {
log.Error(err, "failed to set default value", "spec.background", true)
return nil, ""
}
log.V(3).Info("generated JSON Patch to set default", "spec.background", true)
return patchByte, fmt.Sprintf("default 'Background' to '%s'", strconv.FormatBool(true))
}
return nil, ""
}
func defaultvalidationFailureAction(spec *kyverno.Spec, log logr.Logger) ([]byte, string) {
// set ValidationFailureAction to "audit" if not specified
Audit := kyverno.Audit
if spec.ValidationFailureAction == "" {
log.V(4).Info("setting default value", "spec.validationFailureAction", Audit)
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value string `json:"value"`
}{
"/spec/validationFailureAction",
"add",
string(Audit),
}
patchByte, err := json.Marshal(jsonPatch)
audit := kyverno.Audit
log.V(4).Info("setting default value", "spec.validationFailureAction", audit)
patchByte, err := jsonutils.MarshalPatch("/spec/validationFailureAction", "add", audit)
if err != nil {
log.Error(err, "failed to default value", "spec.validationFailureAction", Audit)
log.Error(err, "failed to default value", "spec.validationFailureAction", audit)
return nil, ""
}
log.V(3).Info("generated JSON Patch to set default", "spec.validationFailureAction", Audit)
return patchByte, fmt.Sprintf("default 'ValidationFailureAction' to '%s'", Audit)
log.V(3).Info("generated JSON Patch to set default", "spec.validationFailureAction", audit)
return patchByte, fmt.Sprintf("default 'ValidationFailureAction' to '%s'", audit)
}
return nil, ""
}
func defaultFailurePolicy(spec *kyverno.Spec, log logr.Logger) ([]byte, string) {
// set failurePolicy to Fail if not present
failurePolicy := string(kyverno.Fail)
if spec.FailurePolicy == nil {
failurePolicy := string(kyverno.Fail)
log.V(4).Info("setting default value", "spec.failurePolicy", failurePolicy)
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value string `json:"value"`
}{
"/spec/failurePolicy",
"add",
string(kyverno.Fail),
}
patchByte, err := json.Marshal(jsonPatch)
patchByte, err := jsonutils.MarshalPatch("/spec/failurePolicy", "add", failurePolicy)
if err != nil {
log.Error(err, "failed to set default value", "spec.failurePolicy", failurePolicy)
return nil, ""
}
log.V(3).Info("generated JSON Patch to set default", "spec.failurePolicy", failurePolicy)
return patchByte, fmt.Sprintf("default failurePolicy to '%s'", failurePolicy)
}
return nil, ""
}
@ -298,34 +239,13 @@ func defaultPodControllerAnnotation(ann map[string]string, controllers string) (
if ann == nil {
ann = make(map[string]string)
ann[kyverno.PodControllersAnnotation] = controllers
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value interface{} `json:"value"`
}{
"/metadata/annotations",
"add",
ann,
}
patchByte, err := json.Marshal(jsonPatch)
patchByte, err := jsonutils.MarshalPatch("/metadata/annotations", "add", ann)
if err != nil {
return nil, err
}
return patchByte, nil
}
jsonPatch := struct {
Path string `json:"path"`
Op string `json:"op"`
Value interface{} `json:"value"`
}{
"/metadata/annotations/pod-policies.kyverno.io~1autogen-controllers",
"add",
controllers,
}
patchByte, err := json.Marshal(jsonPatch)
patchByte, err := jsonutils.MarshalPatch("/metadata/annotations/pod-policies.kyverno.io~1autogen-controllers", "add", controllers)
if err != nil {
return nil, err
}

47
pkg/utils/json/patch.go Normal file
View file

@ -0,0 +1,47 @@
package json
import (
"encoding/json"
jsonpatch "github.com/evanphx/json-patch"
)
type Patch struct {
Path string `json:"path"`
Op string `json:"op"`
Value interface{} `json:"value,omitempty"`
}
func NewPatch(path, op string, value interface{}) Patch {
return Patch{path, op, value}
}
func (p *Patch) Marshal() ([]byte, error) {
return json.Marshal(p)
}
func (p *Patch) ToPatchBytes() ([]byte, error) {
if patch, err := json.Marshal(p); err != nil {
return nil, err
} else {
return JoinPatches(patch), nil
}
}
func MarshalPatch(path, op string, value interface{}) ([]byte, error) {
p := NewPatch(path, op, value)
return p.Marshal()
}
func CheckPatch(patch []byte) error {
_, err := jsonpatch.DecodePatch([]byte("[" + string(patch) + "]"))
return err
}
func UnmarshalPatch(patch []byte) (*Patch, error) {
var p Patch
if err := json.Unmarshal(patch, &p); err != nil {
return nil, err
}
return &p, nil
}

View file

@ -1,7 +1,7 @@
package json
// JoinPatches joins array of serialized JSON patches to the single JSONPatch array
func JoinPatches(patches [][]byte) []byte {
func JoinPatches(patches ...[]byte) []byte {
var result []byte
if len(patches) == 0 {
return result

View file

@ -4,9 +4,9 @@ import (
"encoding/json"
"strings"
jsonpatch "github.com/evanphx/json-patch/v5"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/engine/response"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
yamlv2 "gopkg.in/yaml.v2"
)
@ -21,12 +21,6 @@ type rulePatch struct {
Path string `json:"path"`
}
type annresponse struct {
Op string `json:"op"`
Path string `json:"path"`
Value interface{} `json:"value"`
}
var operationToPastTense = map[string]string{
"add": "added",
"remove": "removed",
@ -39,83 +33,58 @@ var operationToPastTense = map[string]string{
func generateAnnotationPatches(engineResponses []*response.EngineResponse, log logr.Logger) [][]byte {
var annotations map[string]string
var patchBytes [][]byte
for _, er := range engineResponses {
if ann := er.PatchedResource.GetAnnotations(); ann != nil {
annotations = ann
break
}
}
if annotations == nil {
annotations = make(map[string]string)
}
var patchResponse annresponse
var patchResponse jsonutils.Patch
value := annotationFromEngineResponses(engineResponses, log)
if value == nil {
// no patches or error while processing patches
return nil
}
if _, ok := annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")]; ok {
// create update patch string
if _, ok := annotations["policies.kyverno.io/patches"]; ok {
patchResponse = annresponse{
Op: "remove",
Path: "/metadata/annotations/policies.kyverno.io/patches",
}
patchResponse = jsonutils.NewPatch("/metadata/annotations/policies.kyverno.io/patches", "remove", nil)
delete(annotations, "policies.kyverno.io/patches")
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
}
patchResponse = annresponse{
Op: "replace",
Path: "/metadata/annotations/" + policyAnnotation,
Value: string(value),
}
patchResponse = jsonutils.NewPatch("/metadata/annotations/"+policyAnnotation, "replace", string(value))
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
} else {
// mutate rule has annotation patches
if len(annotations) > 0 {
if _, ok := annotations["policies.kyverno.io/patches"]; ok {
patchResponse = annresponse{
Op: "remove",
Path: "/metadata/annotations/" + oldAnnotation,
}
patchResponse = jsonutils.NewPatch("/metadata/annotations/"+oldAnnotation, "remove", nil)
delete(annotations, "policies.kyverno.io/patches")
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
}
patchResponse = annresponse{
Op: "add",
Path: "/metadata/annotations/" + policyAnnotation,
Value: string(value),
}
patchResponse = jsonutils.NewPatch("/metadata/annotations/"+policyAnnotation, "add", string(value))
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
} else {
// insert 'policies.kyverno.patches' entry in annotation map
annotations[strings.ReplaceAll(policyAnnotation, "~1", "/")] = string(value)
patchResponse = annresponse{
Op: "add",
Path: "/metadata/annotations",
Value: annotations,
}
patchResponse = jsonutils.NewPatch("/metadata/annotations", "add", annotations)
patchByte, _ := json.Marshal(patchResponse)
patchBytes = append(patchBytes, patchByte)
}
}
for _, patchByte := range patchBytes {
// check the patch
_, err := jsonpatch.DecodePatch([]byte("[" + string(patchByte) + "]"))
err := jsonutils.CheckPatch(patchByte)
if err != nil {
log.Error(err, "failed to build JSON patch for annotation", "patch", string(patchByte))
}
}
return patchBytes
}
@ -126,12 +95,10 @@ func annotationFromEngineResponses(engineResponses []*response.EngineResponse, l
log.V(3).Info("skip building annotation; policy failed to apply", "policy", engineResponse.PolicyResponse.Policy.Name)
continue
}
rulePatches := annotationFromPolicyResponse(engineResponse.PolicyResponse, log)
if rulePatches == nil {
continue
}
policyName := engineResponse.PolicyResponse.Policy.Name
for _, rulePatch := range rulePatches {
annotationContent[rulePatch.RuleName+"."+policyName+".kyverno.io"] = operationToPastTense[rulePatch.Op] + " " + rulePatch.Path
@ -158,12 +125,11 @@ func annotationFromPolicyResponse(policyResponse response.PolicyResponse, log lo
log.Error(err, "Failed to parse JSON patch bytes")
continue
}
rp := rulePatch{
RuleName: ruleInfo.Name,
Op: patchmap["op"].(string),
Path: patchmap["path"].(string)}
Path: patchmap["path"].(string),
}
rulePatches = append(rulePatches, rp)
log.V(4).Info("annotation value prepared", "patches", rulePatches)
}

View file

@ -43,36 +43,31 @@ func newEngineResponse(policy, rule string, patchesStr []string, status response
func Test_empty_annotation(t *testing.T) {
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, nil)
annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log)
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io/last-applied-patches":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}}`
assert.Assert(t, string(annPatches[0]) == expectedPatches)
expectedPatches := `{"path":"/metadata/annotations","op":"add","value":{"policies.kyverno.io/last-applied-patches":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}}`
assert.Equal(t, string(annPatches[0]), expectedPatches)
}
func Test_exist_annotation(t *testing.T) {
annotation := map[string]interface{}{
"test": "annotation",
}
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, annotation)
annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log)
expectedPatches := `{"op":"add","path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}`
assert.Assert(t, string(annPatches[0]) == expectedPatches)
expectedPatches := `{"path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","op":"add","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}`
assert.Equal(t, string(annPatches[0]), expectedPatches)
}
func Test_exist_kyverno_annotation(t *testing.T) {
annotation := map[string]interface{}{
"policies.kyverno.patches": "old-annotation",
}
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, annotation)
annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log)
expectedPatches := `{"op":"add","path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}`
assert.Assert(t, string(annPatches[0]) == expectedPatches)
expectedPatches := `{"path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","op":"add","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}`
assert.Equal(t, string(annPatches[0]), expectedPatches)
}
func Test_annotation_nil_patch(t *testing.T) {
@ -91,10 +86,8 @@ func Test_annotation_failed_Patch(t *testing.T) {
annotation := map[string]interface{}{
"policies.kyverno.patches": "old-annotation",
}
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, response.RuleStatusFail, annotation)
annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log)
assert.Assert(t, annPatches == nil)
}
@ -105,9 +98,8 @@ func Test_exist_patches(t *testing.T) {
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, response.RuleStatusPass, annotation)
annPatches := generateAnnotationPatches([]*response.EngineResponse{engineResponse}, log.Log)
expectedPatches1 := `{"op":"remove","path":"/metadata/annotations/policies.kyverno.io~1patches","value":null}`
expectedPatches2 := `{"op":"add","path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}`
assert.Assert(t, string(annPatches[0]) == expectedPatches1)
assert.Assert(t, string(annPatches[1]) == expectedPatches2)
expectedPatches1 := `{"path":"/metadata/annotations/policies.kyverno.io~1patches","op":"remove"}`
expectedPatches2 := `{"path":"/metadata/annotations/policies.kyverno.io~1last-applied-patches","op":"add","value":"default-imagepullpolicy.mutate-container.kyverno.io: replaced /spec/containers/0/imagePullPolicy\n"}`
assert.Equal(t, string(annPatches[0]), expectedPatches1)
assert.Equal(t, string(annPatches[1]), expectedPatches2)
}

View file

@ -5,14 +5,13 @@ import (
"reflect"
"time"
"github.com/go-logr/logr"
kyverno "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/common"
"github.com/go-logr/logr"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/utils"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
"github.com/pkg/errors"
"k8s.io/api/admission/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -131,7 +130,7 @@ func (ws *WebhookServer) handleMutation(
}()
// patches holds all the successful patches, if no patch is created, it returns nil
return engineutils.JoinPatches(patches), engineResponses
return jsonutils.JoinPatches(patches...), engineResponses
}
func (ws *WebhookServer) applyMutation(request *v1beta1.AdmissionRequest, policyContext *engine.PolicyContext, logger logr.Logger) (*response.EngineResponse, [][]byte, error) {

View file

@ -7,8 +7,8 @@ import (
v1 "github.com/kyverno/kyverno/api/kyverno/v1"
"github.com/kyverno/kyverno/pkg/engine"
"github.com/kyverno/kyverno/pkg/engine/response"
engineutils "github.com/kyverno/kyverno/pkg/engine/utils"
"github.com/kyverno/kyverno/pkg/policyreport"
jsonutils "github.com/kyverno/kyverno/pkg/utils/json"
"k8s.io/api/admission/v1beta1"
)
@ -51,5 +51,5 @@ func (ws *WebhookServer) handleVerifyImages(request *v1beta1.AdmissionRequest,
return false, getEnforceFailureErrorMsg(engineResponses), nil
}
return true, "", engineutils.JoinPatches(patches)
return true, "", jsonutils.JoinPatches(patches...)
}