1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-15 17:51:20 +00:00

annotations v1

This commit is contained in:
shivkumar dudhani 2019-07-17 15:04:02 -07:00
parent a36ed10425
commit bd9e8585c7
7 changed files with 506 additions and 4 deletions

View file

@ -0,0 +1,220 @@
package annotations
import (
"encoding/json"
"github.com/golang/glog"
"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"`
Rules []Rule `json:"rules,omitempty"`
}
//Rule information for annotations
type Rule struct {
Name string `json:"name"`
Status string `json:"status"`
Type string `json:"type"`
Changes string `json:"changes"`
}
func getStatus(status bool) string {
if status {
return "Success"
}
return "Failure"
}
func getRules(rules []*info.RuleInfo) []Rule {
var annrules []Rule
for _, r := range rules {
annrule := Rule{Name: r.Name,
Status: getStatus(r.IsSuccessful()),
Type: r.RuleType.String()}
//TODO: add mutation changes in policyInfo and in annotation
annrules = append(annrules, annrule)
}
return annrules
}
func (p *Policy) updatePolicy(obj *Policy, ruleType info.RuleType) {
p.Status = obj.Status
p.updatePolicyRules(obj.Rules, ruleType)
}
// Update rules of a given type
func (p *Policy) updatePolicyRules(rules []Rule, ruleType info.RuleType) {
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 *info.PolicyInfo) *Policy {
return &Policy{Status: getStatus(pi.IsSuccessful()),
Rules: getRules(pi.Rules)}
}
//AddPolicy will add policy annotation if not present or update if present
func AddPolicy(obj *unstructured.Unstructured, pi *info.PolicyInfo, ruleType info.RuleType) error {
PolicyObj := newAnnotationForPolicy(pi)
// get annotation
ann := obj.GetAnnotations()
// check if policy already has annotation
cPolicy, ok := ann[pi.Name]
if !ok {
PolicyByte, err := json.Marshal(PolicyObj)
if err != nil {
return err
}
// insert policy information
ann[pi.Name] = string(PolicyByte)
// set annotation back to unstr
obj.SetAnnotations(ann)
return nil
}
cPolicyObj := Policy{}
err := json.Unmarshal([]byte(cPolicy), &cPolicyObj)
// update policy information inside the annotation
// 1> policy status
// 2> rule (name, status,changes,type)
cPolicyObj.updatePolicy(PolicyObj, ruleType)
if err != nil {
return err
}
cPolicyByte, err := json.Marshal(cPolicyObj)
if err != nil {
return err
}
// update policy information
ann[pi.Name] = string(cPolicyByte)
// set annotation back to unstr
obj.SetAnnotations(ann)
return nil
}
//RemovePolicy to remove annotations fro
func RemovePolicy(obj *unstructured.Unstructured, policy string) {
// get annotations
ann := obj.GetAnnotations()
delete(ann, policy)
// set annotation back to unstr
obj.SetAnnotations(ann)
}
//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
}
if annotations, ok := meta["annotations"].(map[string]string); ok {
return annotations
}
return nil
}
//AddPolicyJSONPatch generate JSON Patch to add policy informatino JSON patch
func AddPolicyJSONPatch(ann map[string]string, pi *info.PolicyInfo, ruleType info.RuleType) ([]byte, error) {
if ann == nil {
ann = make(map[string]string, 0)
}
PolicyObj := newAnnotationForPolicy(pi)
cPolicy, ok := ann[pi.Name]
if !ok {
PolicyByte, err := json.Marshal(PolicyObj)
if err != nil {
return nil, err
}
// insert policy information
ann[pi.Name] = string(PolicyByte)
// create add JSON patch
return createAddJSONPatch(ann)
}
cPolicyObj := Policy{}
err := json.Unmarshal([]byte(cPolicy), &cPolicyObj)
// update policy information inside the annotation
// 1> policy status
// 2> rule (name, status,changes,type)
cPolicyObj.updatePolicy(PolicyObj, ruleType)
if err != nil {
return nil, err
}
cPolicyByte, err := json.Marshal(cPolicyObj)
if err != nil {
return nil, err
}
// update policy information
ann[pi.Name] = string(cPolicyByte)
// create update JSON patch
return createReplaceJSONPatch(ann)
}
//RemovePolicyJSONPatch remove JSON patch
func RemovePolicyJSONPatch(ann map[string]string, policy string) ([]byte, error) {
if ann == nil {
return nil, nil
}
delete(ann, policy)
if len(ann) == 0 {
return createRemoveJSONPatch(ann)
}
return createReplaceJSONPatch(ann)
}
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)
}

View file

@ -0,0 +1,25 @@
package annotations
import (
"encoding/json"
"fmt"
"testing"
"github.com/nirmata/kyverno/pkg/info"
)
func TestAddPatch(t *testing.T) {
objRaw := []byte(`{"kind":"Deployment","apiVersion":"apps/v1","metadata":{"name":"nginx-deployment","namespace":"default","creationTimestamp":null,"labels":{"app":"nginx"}},"spec":{"replicas":1,"selector":{"matchLabels":{"app":"nginx"}},"template":{"metadata":{"creationTimestamp":null,"labels":{"app":"nginx"}},"spec":{"containers":[{"name":"nginx","image":"nginx:latest","ports":[{"containerPort":80,"protocol":"TCP"}],"resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"},{"name":"ghost","image":"ghost:latest","resources":{},"terminationMessagePath":"/dev/termination-log","terminationMessagePolicy":"File","imagePullPolicy":"Always"}],"restartPolicy":"Always","terminationGracePeriodSeconds":30,"dnsPolicy":"ClusterFirst","securityContext":{},"schedulerName":"default-scheduler"}},"strategy":{"type":"RollingUpdate","rollingUpdate":{"maxUnavailable":"25%","maxSurge":"25%"}},"revisionHistoryLimit":10,"progressDeadlineSeconds":600},"status":{}}`)
piRaw := []byte(`{"Name":"set-image-pull-policy","RKind":"Deployment","RName":"nginx-deployment","RNamespace":"default","ValidationFailureAction":"","Rules":[{"Name":"nginx-deployment","Msgs":["Rule nginx-deployment: Overlay succesfully applied."],"RuleType":0}]}`)
ann := ParseAnnotationsFromObject(objRaw)
pi := info.PolicyInfo{}
err := json.Unmarshal(piRaw, &pi)
if err != nil {
panic(err)
}
patch, err := AddPolicyJSONPatch(ann, &pi, info.Mutation)
if err != nil {
panic(err)
}
fmt.Println(string(patch))
}

View file

@ -16,6 +16,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
patchTypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/discovery" "k8s.io/client-go/discovery"
"k8s.io/client-go/discovery/cached/memory" "k8s.io/client-go/discovery/cached/memory"
"k8s.io/client-go/dynamic" "k8s.io/client-go/dynamic"
@ -40,7 +41,6 @@ func NewClient(config *rest.Config) (*Client, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
kclient, err := kubernetes.NewForConfig(config) kclient, err := kubernetes.NewForConfig(config)
if err != nil { if err != nil {
return nil, err return nil, err
@ -110,6 +110,11 @@ func (c *Client) GetResource(kind string, namespace string, name string, subreso
return c.getResourceInterface(kind, namespace).Get(name, meta.GetOptions{}, subresources...) return c.getResourceInterface(kind, namespace).Get(name, meta.GetOptions{}, subresources...)
} }
//Patch
func (c *Client) PatchResource(kind string, namespace string, name string, patch []byte) (*unstructured.Unstructured, error) {
return c.getResourceInterface(kind, namespace).Patch(name, patchTypes.JSONPatchType, patch, meta.PatchOptions{})
}
// ListResource returns the list of resources in unstructured/json format // ListResource returns the list of resources in unstructured/json format
// Access items using []Items // Access items using []Items
func (c *Client) ListResource(kind string, namespace string, lselector *meta.LabelSelector) (*unstructured.UnstructuredList, error) { func (c *Client) ListResource(kind string, namespace string, lselector *meta.LabelSelector) (*unstructured.UnstructuredList, error) {

View file

@ -1,9 +1,12 @@
package client package client
import ( import (
"errors"
"strings" "strings"
"time" "time"
"github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/info"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
@ -110,3 +113,215 @@ func retry(attempts int, sleep time.Duration, fn func() error) error {
type stop struct { type stop struct {
error error
} }
func GetAnnotations(obj *unstructured.Unstructured) map[string]interface{} {
var annotationsMaps map[string]interface{}
unstr := obj.UnstructuredContent()
metadata, ok := unstr["metadata"]
if ok {
metadataMap, ok := metadata.(map[string]interface{})
if !ok {
glog.Info("type mismatch")
return nil
}
annotations, ok := metadataMap["annotations"]
if !ok {
glog.Info("annotations not present")
return nil
}
annotationsMaps, ok = annotations.(map[string]interface{})
if !ok {
glog.Info("type mismatch")
return nil
}
}
return annotationsMaps
}
func SetAnnotations(obj *unstructured.Unstructured, annotations map[string]interface{}) error {
unstr := obj.UnstructuredContent()
metadata, ok := unstr["metadata"]
if ok {
metadataMap, ok := metadata.(map[string]interface{})
if !ok {
return errors.New("type mismatch")
}
metadataMap["annotations"] = annotations
unstr["metadata"] = metadataMap
obj.SetUnstructuredContent(unstr)
}
return nil
}
type AnnotationPolicies struct {
// map[policy_name]
Policies map[string]AnnotationPolicy `json:"policies"`
}
type AnnotationPolicy struct {
Status string `json:"status"`
Rules []AnnotationRule `json:"rules,omitempty"`
}
type AnnotationRule struct {
Name string `json:"name"`
Status string `json:"status"`
Type string `json:"type"`
Changes string `json:"changes"`
}
func getStatus(status bool) string {
if status {
return "Success"
}
return "Failure"
}
func getRules(rules []*info.RuleInfo) []AnnotationRule {
var annrules []AnnotationRule
for _, r := range rules {
annrule := AnnotationRule{Name: r.Name,
Status: getStatus(r.IsSuccessful())}
//TODO: add mutation changes in policyInfo and in annotation
annrules = append(annrules, annrule)
}
return annrules
}
// input rules can be mutation or validation
func (ap AnnotationPolicy) updateRules(rules interface{}, validation bool) (error, interface{}) {
ruleList, ok := rules.([]interface{})
updated := false
if !ok {
return errors.New("type mismatch"), false
}
// for mutation rule check if the rules are same
// var mode string
// if validation {
// mode = "Validation"
// } else {
// mode = "Mutation"
// }
// // if lengths are differrent then update
// if len(ruleList) != len(ap.Rules) {
// return nil, ap.updateRules
// }
// check if there is any update in the rules
// order of rules is assumed same while comparison
for i, r := range ruleList {
rule, ok := r.(map[string]interface{})
if !ok {
return errors.New("type mismatch"), nil
}
// Name
name, ok := rule["name"].(string)
if !ok {
return errors.New("type mismatch"), nil
}
if name != ap.Rules[i].Name {
updated = true
break
}
// Status
status, ok := rule["status"].(string)
if !ok {
return errors.New("type mismatch"), nil
}
if status != ap.Rules[i].Status {
updated = true
break
}
}
if updated {
return nil, ap.Rules
}
return nil, nil
}
func newAnnotationPolicy(pi *info.PolicyInfo) AnnotationPolicy {
status := getStatus(pi.IsSuccessful())
rules := getRules(pi.Rules)
return AnnotationPolicy{Status: status,
Rules: rules}
}
//func GetPolicies(policies interface{}) map[string]
func AddPolicy(pi *info.PolicyInfo, ann map[string]interface{}, validation bool) (error, map[string]interface{}) {
// Lets build the policy annotation struct from policyInfo
annpolicy := newAnnotationPolicy(pi)
// Add policy to annotations
// If policy does not exist -> Add
// If already exists then update the status and rules
policies, ok := ann["policies"]
if ok {
policiesMap, ok := policies.(map[string]interface{})
if !ok {
glog.Info("type mismatch")
return errors.New("type mismatch"), nil
}
// check if policy record is present
policy, ok := policiesMap[pi.Name]
if !ok {
// not present then we add
policiesMap[pi.Name] = annpolicy
ann["policies"] = policiesMap
return nil, ann
}
policyMap, ok := policy.(map[string]interface{})
if !ok {
return errors.New("type mismatch"), nil
}
// We just update the annotations
// status
status := policyMap["status"]
statusStr, ok := status.(string)
if !ok {
return errors.New("type mismatch"), nil
}
if statusStr != annpolicy.Status {
policyMap["status"] = annpolicy.Status
}
// check rules
rules, ok := policyMap["rules"]
if !ok {
return errors.New("no rules"), nil
}
err, newRules := annpolicy.updateRules(rules, validation)
if err != nil {
return err, nil
}
if newRules == nil {
//nothing to update
return nil, nil
}
// update the new rule
policyMap["rules"] = newRules
// update policies map
policiesMap[pi.Name] = policyMap
ann["policies"] = policiesMap
return nil, ann
}
return nil, nil
}
// RemovePolicy
func RemovePolicy(pi *info.PolicyInfo, ann map[string]interface{}) (error, map[string]interface{}) {
policies, ok := ann["policies"]
if ok {
policiesMap, ok := policies.(map[string]interface{})
if !ok {
glog.Info("type mismatch")
return errors.New("type mismatch"), nil
}
// check if policy record is present
_, ok = policiesMap[pi.Name]
if ok {
// delete the pair
delete(policiesMap, pi.Name)
ann["policies"] = policiesMap
return nil, ann
}
}
return nil, nil
}

View file

@ -103,7 +103,8 @@ func applyPolicyOnRaw(policy *kubepolicy.Policy, rawResource []byte, gvk *metav1
policyInfo := info.NewPolicyInfo(policy.Name, policyInfo := info.NewPolicyInfo(policy.Name,
gvk.Kind, gvk.Kind,
rname, rname,
rns) rns,
policy.Spec.ValidationFailureAction)
// Process Mutation // Process Mutation
patches, ruleInfos := engine.Mutate(*policy, rawResource, *gvk) patches, ruleInfos := engine.Mutate(*policy, rawResource, *gvk)

View file

@ -80,7 +80,7 @@ func scanDir(dir string) ([]string, error) {
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if err != nil { if err != nil {
return fmt.Errorf("prevent panic by handling failure accessing a path %q: %v\n", dir, err) return fmt.Errorf("prevent panic by handling failure accessing a path %q: %v", dir, err)
} }
res = append(res, path) res = append(res, path)

View file

@ -1,7 +1,12 @@
package webhooks package webhooks
import ( import (
"fmt"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog" "github.com/golang/glog"
"github.com/nirmata/kyverno/pkg/annotations"
engine "github.com/nirmata/kyverno/pkg/engine" engine "github.com/nirmata/kyverno/pkg/engine"
"github.com/nirmata/kyverno/pkg/info" "github.com/nirmata/kyverno/pkg/info"
v1beta1 "k8s.io/api/admission/v1beta1" v1beta1 "k8s.io/api/admission/v1beta1"
@ -23,6 +28,7 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
} }
var allPatches [][]byte var allPatches [][]byte
var annPatches []byte
policyInfos := []*info.PolicyInfo{} policyInfos := []*info.PolicyInfo{}
for _, policy := range policies { for _, policy := range policies {
// check if policy has a rule for the admission request kind // check if policy has a rule for the admission request kind
@ -65,6 +71,18 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
} }
} }
policyInfos = append(policyInfos, policyInfo) policyInfos = append(policyInfos, policyInfo)
annPatch := addAnnotationsToResource(request.Object.Raw, policyInfo, info.Mutation)
if annPatch != nil {
if annPatches == nil {
annPatches = annPatch
} else {
annPatches, err = jsonpatch.MergePatch(annPatches, annPatch)
if err != nil {
fmt.Println("Mergining docs")
fmt.Println(err)
}
}
}
} }
if len(allPatches) > 0 { if len(allPatches) > 0 {
@ -74,10 +92,17 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
ok, msg := isAdmSuccesful(policyInfos) ok, msg := isAdmSuccesful(policyInfos)
if ok { if ok {
patches := engine.JoinPatches(allPatches)
if len(annPatches) > 0 {
patches, err = jsonpatch.MergePatch(patches, annPatches)
if err != nil {
fmt.Println(err)
}
}
patchType := v1beta1.PatchTypeJSONPatch patchType := v1beta1.PatchTypeJSONPatch
return &v1beta1.AdmissionResponse{ return &v1beta1.AdmissionResponse{
Allowed: true, Allowed: true,
Patch: engine.JoinPatches(allPatches), Patch: patches,
PatchType: &patchType, PatchType: &patchType,
} }
} }
@ -88,3 +113,14 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be
}, },
} }
} }
func addAnnotationsToResource(rawResource []byte, pi *info.PolicyInfo, ruleType info.RuleType) []byte {
// get annotations
ann := annotations.ParseAnnotationsFromObject(rawResource)
patch, err := annotations.AddPolicyJSONPatch(ann, pi, ruleType)
if err != nil {
fmt.Println(err)
return nil
}
return patch
}