1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2025-03-09 17:37:12 +00:00
kyverno/pkg/annotations/annotations.go
2019-07-18 15:49:29 -07:00

317 lines
8.2 KiB
Go

package annotations
import (
"encoding/json"
"reflect"
"github.com/golang/glog"
pinfo "github.com/nirmata/kyverno/pkg/info"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
)
//Policy information for annotations
type Policy struct {
Status string `json:"status"`
// Key Type/Name
MutationRules map[string]Rule `json:"mutationrules,omitempty"`
ValidationRules map[string]Rule `json:"validationrules,omitempty"`
GenerationRules map[string]Rule `json:"generationrules,omitempty"`
}
//Rule information for annotations
type Rule struct {
Status string `json:"status"`
Changes string `json:"changes,omitempty"` // TODO for mutation changes
}
func getStatus(status bool) string {
if status {
return "Success"
}
return "Failure"
}
func buildKey(policyName string) string {
return "policies.kyverno.io." + policyName
}
func getRules(rules []*pinfo.RuleInfo, ruleType pinfo.RuleType) map[string]Rule {
if len(rules) == 0 {
return nil
}
annrules := make(map[string]Rule, 0)
// var annrules map[string]Rule
for _, r := range rules {
if r.RuleType != ruleType {
continue
}
annrules[r.Name] =
Rule{Status: getStatus(r.IsSuccessful())}
// //TODO: add mutation changes in policyInfo and in annotation
// annrules = append(annrules, annrule)
}
return annrules
}
func (p *Policy) updatePolicy(obj *Policy, ruleType pinfo.RuleType) bool {
updates := false
if p.Status != obj.Status {
updates = true
}
p.Status = obj.Status
// Check Mutation rules
switch ruleType {
case pinfo.Mutation:
if p.compareMutationRules(obj.MutationRules) {
updates = true
}
case pinfo.Validation:
if p.compareValidationRules(obj.ValidationRules) {
updates = true
}
case pinfo.Generation:
if p.compareGenerationRules(obj.GenerationRules) {
updates = true
}
}
// If there are any updates then the annotation can be updated, can skip
return updates
}
func (p *Policy) compareMutationRules(rules map[string]Rule) bool {
// check if the rules have changed
if !reflect.DeepEqual(p.MutationRules, rules) {
p.MutationRules = rules
return true
}
return false
}
func (p *Policy) compareValidationRules(rules map[string]Rule) bool {
// check if the rules have changed
if !reflect.DeepEqual(p.ValidationRules, rules) {
p.ValidationRules = rules
return true
}
return false
}
func (p *Policy) compareGenerationRules(rules map[string]Rule) bool {
// check if the rules have changed
if !reflect.DeepEqual(p.GenerationRules, rules) {
p.GenerationRules = rules
return true
}
return false
}
// // Update rules of a given type
// func (p *Policy) updatePolicyRules(rules map[string]Rule, ruleType info.RuleType) bool {
// updates := false
// // Check if new rules are present in existing rules
// // for k, v := range rules {
// // _,
// // }
// var updatedRules []Rule
// //TODO: check the selecting update add advantage
// // filter rules for different type
// for _, r := range rules {
// if r.Type != ruleType.String() {
// updatedRules = append(updatedRules, r)
// }
// }
// // Add rules for current type
// updatedRules = append(updatedRules, rules...)
// // set the rule
// p.Rules = updatedRules
// }
// func (p *Policy) containsPolicyRules(rules []Rule, ruleType info.RuleType) {
// for _, r := range rules {
// }
// }
func newAnnotationForPolicy(pi *pinfo.PolicyInfo) *Policy {
return &Policy{Status: getStatus(pi.IsSuccessful()),
MutationRules: getRules(pi.Rules, pinfo.Mutation),
ValidationRules: getRules(pi.Rules, pinfo.Validation),
GenerationRules: getRules(pi.Rules, pinfo.Generation),
}
}
//AddPolicy will add policy annotation if not present or update if present
// modifies obj
// returns true, if there is any update -> caller need to update the obj
// returns false, if there is no change -> caller can skip the update
func AddPolicy(obj *unstructured.Unstructured, pi *pinfo.PolicyInfo, ruleType pinfo.RuleType) bool {
PolicyObj := newAnnotationForPolicy(pi)
// get annotation
ann := obj.GetAnnotations()
// check if policy already has annotation
cPolicy, ok := ann[buildKey(pi.Name)]
if !ok {
PolicyByte, err := json.Marshal(PolicyObj)
if err != nil {
glog.Error(err)
return false
}
// insert policy information
ann[buildKey(pi.Name)] = string(PolicyByte)
// set annotation back to unstr
obj.SetAnnotations(ann)
return true
}
cPolicyObj := Policy{}
err := json.Unmarshal([]byte(cPolicy), &cPolicyObj)
if err != nil {
return false
}
// update policy information inside the annotation
// 1> policy status
// 2> Mutation, Validation, Generation
if cPolicyObj.updatePolicy(PolicyObj, ruleType) {
cPolicyByte, err := json.Marshal(cPolicyObj)
if err != nil {
return false
}
// update policy information
ann[buildKey(pi.Name)] = string(cPolicyByte)
// set annotation back to unstr
obj.SetAnnotations(ann)
return true
}
return false
}
//RemovePolicy to remove annotations
// return true -> if there was an entry and we deleted it
// return false -> if there is no entry, caller need not update
func RemovePolicy(obj *unstructured.Unstructured, policy string) bool {
// get annotations
ann := obj.GetAnnotations()
if ann == nil {
return false
}
if _, ok := ann[buildKey(policy)]; !ok {
return false
}
delete(ann, buildKey(policy))
// set annotation back to unstr
obj.SetAnnotations(ann)
return true
}
//ParseAnnotationsFromObject extracts annotations from the JSON obj
func ParseAnnotationsFromObject(bytes []byte) map[string]string {
var objectJSON map[string]interface{}
json.Unmarshal(bytes, &objectJSON)
meta, ok := objectJSON["metadata"].(map[string]interface{})
if !ok {
glog.Error("unable to parse")
return nil
}
ann, ok, err := unstructured.NestedStringMap(meta, "annotations")
if err != nil || !ok {
return nil
}
return ann
}
//AddPolicyJSONPatch generate JSON Patch to add policy informatino JSON patch
func AddPolicyJSONPatch(ann map[string]string, pi *pinfo.PolicyInfo, ruleType pinfo.RuleType) (map[string]string, []byte, error) {
if ann == nil {
ann = make(map[string]string, 0)
}
PolicyObj := newAnnotationForPolicy(pi)
cPolicy, ok := ann[buildKey(pi.Name)]
if !ok {
PolicyByte, err := json.Marshal(PolicyObj)
if err != nil {
return nil, nil, err
}
// insert policy information
ann[buildKey(pi.Name)] = string(PolicyByte)
// create add JSON patch
jsonPatch, err := createAddJSONPatch(ann)
// var jsonPatch []byte
// if len(ann) == 0 {
// jsonPatch, err = createAddJSONPatch(ann)
// } else {
// jsonPatch, err = createReplaceJSONPatch(ann)
// }
return ann, jsonPatch, err
}
cPolicyObj := Policy{}
err := json.Unmarshal([]byte(cPolicy), &cPolicyObj)
// update policy information inside the annotation
// 1> policy status
// 2> rule (name, status,changes,type)
update := cPolicyObj.updatePolicy(PolicyObj, ruleType)
if !update {
return nil, nil, err
}
cPolicyByte, err := json.Marshal(cPolicyObj)
if err != nil {
return nil, nil, err
}
// update policy information
ann[buildKey(pi.Name)] = string(cPolicyByte)
// create update JSON patch
jsonPatch, err := createReplaceJSONPatch(ann)
return ann, jsonPatch, err
}
//RemovePolicyJSONPatch remove JSON patch
func RemovePolicyJSONPatch(ann map[string]string, policy string) (map[string]string, []byte, error) {
if ann == nil {
return nil, nil, nil
}
delete(ann, policy)
if len(ann) == 0 {
jsonPatch, err := createRemoveJSONPatch(ann)
return nil, jsonPatch, err
}
jsonPatch, err := createReplaceJSONPatch(ann)
return ann, jsonPatch, err
}
type patchMapValue struct {
Op string `json:"op"`
Path string `json:"path"`
Value map[string]string `json:"value"`
}
func createRemoveJSONPatch(ann map[string]string) ([]byte, error) {
payload := []patchMapValue{{
Op: "remove",
Path: "/metadata/annotations",
}}
return json.Marshal(payload)
}
func createAddJSONPatch(ann map[string]string) ([]byte, error) {
if ann == nil {
ann = make(map[string]string, 0)
}
payload := []patchMapValue{{
Op: "add",
Path: "/metadata/annotations",
Value: ann,
}}
return json.Marshal(payload)
}
func createReplaceJSONPatch(ann map[string]string) ([]byte, error) {
if ann == nil {
ann = make(map[string]string, 0)
}
payload := []patchMapValue{{
Op: "replace",
Path: "/metadata/annotations",
Value: ann,
}}
return json.Marshal(payload)
}