mirror of
https://github.com/kyverno/kyverno.git
synced 2024-12-15 17:51:20 +00:00
fix 365 annotation_bug
This commit is contained in:
parent
ed960ad277
commit
2077409c85
4 changed files with 153 additions and 107 deletions
|
@ -11,12 +11,10 @@ import (
|
|||
|
||||
const (
|
||||
policyAnnotation = "policies.kyverno.io"
|
||||
// lastAppliedPatches = policyAnnotation + "last-applied-patches"
|
||||
)
|
||||
|
||||
type policyPatch struct {
|
||||
PolicyName string `json:"policyname"`
|
||||
// RulePatches []string `json:"patches"`
|
||||
PolicyName string `json:"policyname"`
|
||||
RulePatches interface{} `json:"patches"`
|
||||
}
|
||||
|
||||
|
@ -32,28 +30,32 @@ type response struct {
|
|||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
func generateAnnotationPatches(annotations map[string]string, policyResponse engine.PolicyResponse) []byte {
|
||||
func generateAnnotationPatches(annotations map[string]string, engineResponses []engine.EngineResponseNew) []byte {
|
||||
if annotations == nil {
|
||||
annotations = map[string]string{}
|
||||
annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
var patchResponse response
|
||||
value := generateAnnotationsFromPolicyResponse(policyResponse)
|
||||
value := annotationFromPolicyResponses(engineResponses)
|
||||
if value == nil {
|
||||
// no patches or error while processing patches
|
||||
return nil
|
||||
}
|
||||
|
||||
if _, ok := annotations[policyAnnotation]; ok {
|
||||
// create update patch string
|
||||
patchResponse = response{
|
||||
Path: "/metadata/annotations/" + policyAnnotation,
|
||||
Op: "replace",
|
||||
Path: "/metadata/annotations/" + policyAnnotation,
|
||||
Value: string(value),
|
||||
}
|
||||
} else {
|
||||
// insert 'policies.kyverno.io' entry in annotation map
|
||||
annotations[policyAnnotation] = string(value)
|
||||
patchResponse = response{
|
||||
Path: "/metadata/annotations",
|
||||
Op: "add",
|
||||
Value: map[string]string{policyAnnotation: string(value)},
|
||||
Path: "/metadata/annotations",
|
||||
Value: annotations,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -68,109 +70,59 @@ func generateAnnotationPatches(annotations map[string]string, policyResponse eng
|
|||
return patchByte
|
||||
}
|
||||
|
||||
// func prepareAnnotationPatches(resource *unstructured.Unstructured, policyInfos []info.PolicyInfo) []byte {
|
||||
// annots := resource.GetAnnotations()
|
||||
// if annots == nil {
|
||||
// annots = map[string]string{}
|
||||
// }
|
||||
|
||||
// var patchResponse response
|
||||
// value := annotationFromPolicies(policyInfos)
|
||||
// if _, ok := annots[policyAnnotation]; ok {
|
||||
// // create update patch string
|
||||
// patchResponse = response{
|
||||
// Op: "replace",
|
||||
// Path: "/metadata/annotations/" + policyAnnotation,
|
||||
// Value: string(value),
|
||||
// }
|
||||
// } else {
|
||||
// patchResponse = response{
|
||||
// Op: "add",
|
||||
// Path: "/metadata/annotations",
|
||||
// Value: map[string]string{policyAnnotation: string(value)},
|
||||
// }
|
||||
// }
|
||||
|
||||
// patchByte, _ := json.Marshal(patchResponse)
|
||||
|
||||
// // check the patch
|
||||
// _, err := jsonpatch.DecodePatch([]byte("[" + string(patchByte) + "]"))
|
||||
// if err != nil {
|
||||
// glog.Errorf("Failed to make patch from annotation'%s', err: %v\n ", string(patchByte), err)
|
||||
// }
|
||||
|
||||
// return patchByte
|
||||
// }
|
||||
|
||||
// func annotationFromPolicies(policyInfos []info.PolicyInfo) []byte {
|
||||
// var policyPatches []policyPatch
|
||||
// for _, policyInfo := range policyInfos {
|
||||
// var pp policyPatch
|
||||
|
||||
// pp.PolicyName = policyInfo.Name
|
||||
// pp.RulePatches = annotationFromPolicy(policyInfo)
|
||||
// policyPatches = append(policyPatches, pp)
|
||||
// }
|
||||
|
||||
// result, _ := json.Marshal(policyPatches)
|
||||
|
||||
// return result
|
||||
// }
|
||||
|
||||
func generateAnnotationsFromPolicyResponse(policyResponse engine.PolicyResponse) []byte {
|
||||
var rulePatches []rulePatch
|
||||
// generate annotation for each mutation JSON patch to be applied on the resource
|
||||
for _, rule := range policyResponse.Rules {
|
||||
var patchmap map[string]string
|
||||
patch := engine.JoinPatches(rule.Patches)
|
||||
if err := json.Unmarshal(patch, &patchmap); err != nil {
|
||||
glog.Errorf("Failed to parse patch bytes, err: %v\n", err)
|
||||
func annotationFromPolicyResponses(engineResponses []engine.EngineResponseNew) []byte {
|
||||
var policyPatches []policyPatch
|
||||
for _, engineResponse := range engineResponses {
|
||||
if !engineResponse.IsSuccesful() {
|
||||
glog.V(3).Infof("Policy %s failed, skip preparing annotation\n", engineResponse.PolicyResponse.Policy)
|
||||
continue
|
||||
}
|
||||
|
||||
rp := rulePatch{
|
||||
RuleName: rule.Name,
|
||||
Op: patchmap["op"],
|
||||
Path: patchmap["path"]}
|
||||
|
||||
rulePatches = append(rulePatches, rp)
|
||||
glog.V(4).Infof("Annotation value prepared: %v\n", rulePatches)
|
||||
var pp policyPatch
|
||||
rulePatches := annotationFromPolicyResponse(engineResponse.PolicyResponse)
|
||||
if rulePatches == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
pp.RulePatches = rulePatches
|
||||
pp.PolicyName = engineResponse.PolicyResponse.Policy
|
||||
policyPatches = append(policyPatches, pp)
|
||||
}
|
||||
patch, err := json.Marshal(rulePatches)
|
||||
if err != nil {
|
||||
glog.Infof("failed to marshall: %v", err)
|
||||
|
||||
// return nil if there's no patches
|
||||
// otherwise result = null, len(result) = 4
|
||||
if policyPatches == nil {
|
||||
return nil
|
||||
}
|
||||
return patch
|
||||
|
||||
result, _ := json.Marshal(policyPatches)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// func annotationFromPolicy(policyInfo info.PolicyInfo) []rulePatch {
|
||||
// if !policyInfo.IsSuccessful() {
|
||||
// glog.V(2).Infof("Policy %s failed, skip preparing annotation\n", policyInfo.Name)
|
||||
// return nil
|
||||
// }
|
||||
func annotationFromPolicyResponse(policyResponse engine.PolicyResponse) []rulePatch {
|
||||
var rulePatches []rulePatch
|
||||
for _, ruleInfo := range policyResponse.Rules {
|
||||
for _, patch := range ruleInfo.Patches {
|
||||
var patchmap map[string]interface{}
|
||||
if err := json.Unmarshal(patch, &patchmap); err != nil {
|
||||
glog.Errorf("Failed to parse patch bytes, err: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// var rulePatches []rulePatch
|
||||
// for _, ruleInfo := range policyInfo.Rules {
|
||||
rp := rulePatch{
|
||||
RuleName: ruleInfo.Name,
|
||||
Op: patchmap["op"].(string),
|
||||
Path: patchmap["path"].(string)}
|
||||
|
||||
// for _, patch := range ruleInfo.Patches {
|
||||
// var patchmap map[string]string
|
||||
rulePatches = append(rulePatches, rp)
|
||||
glog.V(4).Infof("Annotation value prepared: %v\n", rulePatches)
|
||||
}
|
||||
}
|
||||
|
||||
// if err := json.Unmarshal(patch, &patchmap); err != nil {
|
||||
// glog.Errorf("Failed to parse patch bytes, err: %v\n", err)
|
||||
// continue
|
||||
// }
|
||||
if len(rulePatches) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// rp := rulePatch{
|
||||
// RuleName: ruleInfo.Name,
|
||||
// Op: patchmap["op"],
|
||||
// Path: patchmap["path"]}
|
||||
|
||||
// rulePatches = append(rulePatches, rp)
|
||||
// glog.V(4).Infof("Annotation value prepared: %v\n", rulePatches)
|
||||
// }
|
||||
// }
|
||||
|
||||
// return rulePatches
|
||||
// }
|
||||
return rulePatches
|
||||
}
|
||||
|
|
92
pkg/webhooks/annotations_test.go
Normal file
92
pkg/webhooks/annotations_test.go
Normal file
|
@ -0,0 +1,92 @@
|
|||
package webhooks
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nirmata/kyverno/pkg/engine"
|
||||
"gotest.tools/assert"
|
||||
)
|
||||
|
||||
func newPolicyResponse(policy, rule string, patchesStr []string, success bool) engine.PolicyResponse {
|
||||
var patches [][]byte
|
||||
for _, p := range patchesStr {
|
||||
patches = append(patches, []byte(p))
|
||||
}
|
||||
|
||||
return engine.PolicyResponse{
|
||||
Policy: policy,
|
||||
Rules: []engine.RuleResponse{
|
||||
engine.RuleResponse{
|
||||
Name: rule,
|
||||
Patches: patches,
|
||||
Success: success},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newEngineResponse(policy, rule string, patchesStr []string, success bool) engine.EngineResponseNew {
|
||||
return engine.EngineResponseNew{
|
||||
PolicyResponse: newPolicyResponse(policy, rule, patchesStr, success),
|
||||
}
|
||||
}
|
||||
|
||||
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}, true)
|
||||
|
||||
annPatches := generateAnnotationPatches(nil, []engine.EngineResponseNew{engineResponse})
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}}`
|
||||
assert.Assert(t, string(annPatches) == expectedPatches)
|
||||
}
|
||||
|
||||
func Test_exist_annotation(t *testing.T) {
|
||||
annotation := map[string]string{
|
||||
"test": "annotation",
|
||||
}
|
||||
|
||||
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponseNew{engineResponse})
|
||||
|
||||
expectedPatches := `{"op":"add","path":"/metadata/annotations","value":{"policies.kyverno.io":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]","test":"annotation"}}`
|
||||
assert.Assert(t, string(annPatches) == expectedPatches)
|
||||
}
|
||||
|
||||
func Test_exist_kyverno_annotation(t *testing.T) {
|
||||
annotation := map[string]string{
|
||||
"policies.kyverno.io": "old-annotation",
|
||||
}
|
||||
|
||||
patchStr := `{ "op": "replace", "path": "/spec/containers/0/imagePullPolicy", "value": "IfNotPresent" }`
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{patchStr}, true)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponseNew{engineResponse})
|
||||
|
||||
expectedPatches := `{"op":"replace","path":"/metadata/annotations/policies.kyverno.io","value":"[{\"policyname\":\"mutate-container\",\"patches\":[{\"rulename\":\"default-imagepullpolicy\",\"op\":\"replace\",\"path\":\"/spec/containers/0/imagePullPolicy\"}]}]"}`
|
||||
assert.Assert(t, string(annPatches) == expectedPatches)
|
||||
}
|
||||
|
||||
func Test_annotation_nil_patch(t *testing.T) {
|
||||
annotation := map[string]string{
|
||||
"policies.kyverno.io": "old-annotation",
|
||||
}
|
||||
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, true)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponseNew{engineResponse})
|
||||
|
||||
assert.Assert(t, annPatches == nil)
|
||||
|
||||
engineResponseNew := newEngineResponse("mutate-container", "default-imagepullpolicy", []string{""}, true)
|
||||
annPatchesNew := generateAnnotationPatches(annotation, []engine.EngineResponseNew{engineResponseNew})
|
||||
assert.Assert(t, annPatchesNew == nil)
|
||||
}
|
||||
|
||||
func Test_annotation_failed_Patch(t *testing.T) {
|
||||
annotation := map[string]string{
|
||||
"policies.kyverno.io": "old-annotation",
|
||||
}
|
||||
|
||||
engineResponse := newEngineResponse("mutate-container", "default-imagepullpolicy", nil, false)
|
||||
annPatches := generateAnnotationPatches(annotation, []engine.EngineResponseNew{engineResponse})
|
||||
|
||||
assert.Assert(t, annPatches == nil)
|
||||
}
|
|
@ -91,15 +91,17 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) (bool
|
|||
}
|
||||
// gather patches
|
||||
patches = append(patches, engineResponse.GetPatches()...)
|
||||
// generate annotations
|
||||
if annPatches := generateAnnotationPatches(resource.GetAnnotations(), engineResponse.PolicyResponse); annPatches != nil {
|
||||
patches = append(patches, annPatches)
|
||||
}
|
||||
|
||||
glog.V(4).Infof("Mutation from policy %s has applied succesfully to %s %s/%s", policy.Name, request.Kind.Kind, resource.GetNamespace(), resource.GetName())
|
||||
//TODO: check if there is an order to policy application on resource
|
||||
// resource = &engineResponse.PatchedResource
|
||||
}
|
||||
|
||||
// generate annotations
|
||||
if annPatches := generateAnnotationPatches(resource.GetAnnotations(), engineResponses); annPatches != nil {
|
||||
patches = append(patches, annPatches)
|
||||
}
|
||||
|
||||
// ADD EVENTS
|
||||
events := generateEvents(engineResponses, (request.Operation == v1beta1.Update))
|
||||
ws.eventGen.Add(events...)
|
||||
|
|
|
@ -86,7 +86,7 @@ func processResourceWithPatches(patch []byte, resource []byte) []byte {
|
|||
if patch == nil {
|
||||
return nil
|
||||
}
|
||||
glog.Info(string(resource))
|
||||
|
||||
resource, err := engine.ApplyPatchNew(resource, patch)
|
||||
if err != nil {
|
||||
glog.Errorf("failed to patch resource: %v", err)
|
||||
|
|
Loading…
Reference in a new issue