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:
parent
adca5f200b
commit
663ad49dca
14 changed files with 132 additions and 293 deletions
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{}
|
||||
|
|
|
@ -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{
|
||||
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{
|
||||
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{
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,24 +132,17 @@ 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)
|
||||
}
|
||||
}
|
||||
return strings.Join(resultPath, ";")
|
||||
}
|
||||
|
||||
|
|
|
@ -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
47
pkg/utils/json/patch.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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...)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue