From d593fe1a92217778d6facfbcab9a8b006e7420d8 Mon Sep 17 00:00:00 2001 From: belyshevdenis Date: Fri, 1 Mar 2019 14:16:20 +0200 Subject: [PATCH] NK-22: Fixed build error with Selector pointer. Added comments. Changed tab to 4 spaces identation. Added unit tests for LabelSelector. --- controller/controller.go | 130 ++++++------ crd/sample-policy.yaml | 8 +- crd/selector-policy.yaml | 2 +- main.go | 74 +++---- pkg/apis/policy/register.go | 2 +- pkg/apis/policy/v1alpha1/register.go | 2 +- pkg/apis/policy/v1alpha1/types.go | 52 ++--- server/server.go | 230 ++++++++++---------- webhooks/admission.go | 153 +++++++------- webhooks/admission_test.go | 305 +++++++++++++++++---------- webhooks/mutation.go | 188 +++++++++-------- webhooks/mutation_test.go | 58 ++--- webhooks/utils_test.go | 36 ++-- 13 files changed, 669 insertions(+), 571 deletions(-) diff --git a/controller/controller.go b/controller/controller.go index a4a56c36c0..411ad0f93a 100644 --- a/controller/controller.go +++ b/controller/controller.go @@ -1,111 +1,111 @@ package controller import ( - "log" - "os" - "time" + "log" + "os" + "time" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/clientcmd" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/clientcmd" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - clientset "github.com/nirmata/kube-policy/pkg/client/clientset/versioned" - informers "github.com/nirmata/kube-policy/pkg/client/informers/externalversions" - lister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1" + types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + clientset "github.com/nirmata/kube-policy/pkg/client/clientset/versioned" + informers "github.com/nirmata/kube-policy/pkg/client/informers/externalversions" + lister "github.com/nirmata/kube-policy/pkg/client/listers/policy/v1alpha1" ) // PolicyController for CRD type PolicyController struct { - policyInformerFactory informers.SharedInformerFactory - policyLister lister.PolicyLister - logger *log.Logger + policyInformerFactory informers.SharedInformerFactory + policyLister lister.PolicyLister + logger *log.Logger } // NewPolicyController from cmd args func NewPolicyController(masterURL, kubeconfigPath string, logger *log.Logger) (*PolicyController, error) { - if logger == nil { - logger = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile) - } + if logger == nil { + logger = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile) + } - cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfigPath) - if err != nil { - logger.Printf("Error building kubeconfig: %v\n", err) - return nil, err - } + cfg, err := clientcmd.BuildConfigFromFlags(masterURL, kubeconfigPath) + if err != nil { + logger.Printf("Error building kubeconfig: %v\n", err) + return nil, err + } - policyClientset, err := clientset.NewForConfig(cfg) - if err != nil { - logger.Printf("Error building policy clientset: %v\n", err) - return nil, err - } + policyClientset, err := clientset.NewForConfig(cfg) + if err != nil { + logger.Printf("Error building policy clientset: %v\n", err) + return nil, err + } - policyInformerFactory := informers.NewSharedInformerFactory(policyClientset, time.Second*30) - policyInformer := policyInformerFactory.Nirmata().V1alpha1().Policies() + policyInformerFactory := informers.NewSharedInformerFactory(policyClientset, time.Second*30) + policyInformer := policyInformerFactory.Nirmata().V1alpha1().Policies() - controller := &PolicyController{ - policyInformerFactory: policyInformerFactory, - policyLister: policyInformer.Lister(), - logger: logger, - } + controller := &PolicyController{ + policyInformerFactory: policyInformerFactory, + policyLister: policyInformer.Lister(), + logger: logger, + } - policyInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ - AddFunc: controller.createPolicyHandler, - UpdateFunc: controller.updatePolicyHandler, - DeleteFunc: controller.deletePolicyHandler, - }) + policyInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: controller.createPolicyHandler, + UpdateFunc: controller.updatePolicyHandler, + DeleteFunc: controller.deletePolicyHandler, + }) - return controller, nil + return controller, nil } // Run is main controller thread func (c *PolicyController) Run(stopCh <-chan struct{}) { - c.policyInformerFactory.Start(stopCh) + c.policyInformerFactory.Start(stopCh) } // GetPolicies retrieves all policy resources // from cache. Cache is refreshed by informer func (c *PolicyController) GetPolicies() []types.Policy { - // Create nil Selector to grab all the policies - selector := labels.NewSelector() - cachedPolicies, err := c.policyLister.List(selector) + // Create nil Selector to grab all the policies + selector := labels.NewSelector() + cachedPolicies, err := c.policyLister.List(selector) - if err != nil { - c.logger.Printf("Error: %v", err) - return nil - } + if err != nil { + c.logger.Printf("Error: %v", err) + return nil + } - var policies []types.Policy - for _, elem := range cachedPolicies { - policies = append(policies, *elem.DeepCopy()) - } + var policies []types.Policy + for _, elem := range cachedPolicies { + policies = append(policies, *elem.DeepCopy()) + } - return policies + return policies } func (c *PolicyController) createPolicyHandler(resource interface{}) { - key := c.getResourceKey(resource) - c.logger.Printf("Created policy: %s\n", key) + key := c.getResourceKey(resource) + c.logger.Printf("Created policy: %s\n", key) } func (c *PolicyController) updatePolicyHandler(oldResource, newResource interface{}) { - oldKey := c.getResourceKey(oldResource) - newKey := c.getResourceKey(newResource) + oldKey := c.getResourceKey(oldResource) + newKey := c.getResourceKey(newResource) - c.logger.Printf("Updated policy from %s to %s\n", oldKey, newKey) + c.logger.Printf("Updated policy from %s to %s\n", oldKey, newKey) } func (c *PolicyController) deletePolicyHandler(resource interface{}) { - key := c.getResourceKey(resource) - c.logger.Printf("Deleted policy: %s\n", key) + key := c.getResourceKey(resource) + c.logger.Printf("Deleted policy: %s\n", key) } func (c *PolicyController) getResourceKey(resource interface{}) string { - if key, err := cache.MetaNamespaceKeyFunc(resource); err != nil { - c.logger.Fatalf("Error retrieving policy key: %v\n", err) - } else { - return key - } + if key, err := cache.MetaNamespaceKeyFunc(resource); err != nil { + c.logger.Fatalf("Error retrieving policy key: %v\n", err) + } else { + return key + } - return "" + return "" } diff --git a/crd/sample-policy.yaml b/crd/sample-policy.yaml index 15fc65fee5..8a9de6aecd 100644 --- a/crd/sample-policy.yaml +++ b/crd/sample-policy.yaml @@ -1,7 +1,7 @@ -apiVersion: nirmata.io/v1alpha1 +apiVersion: policy.nirmata.io/v1alpha1 kind: Policy metadata: - name: hello-policy + name: hello-policy spec: - policySpec: 'hi' - image: hello-policy-image + policySpec: 'hi' + image: hello-policy-image diff --git a/crd/selector-policy.yaml b/crd/selector-policy.yaml index 9fdd2df055..8269eb55fe 100644 --- a/crd/selector-policy.yaml +++ b/crd/selector-policy.yaml @@ -1,4 +1,4 @@ -apiVersion: nirmata.io/v1alpha1 +apiVersion: policy.nirmata.io/v1alpha1 kind : Policy metadata: name: selector-policy diff --git a/main.go b/main.go index ee8437553c..82450b1f27 100644 --- a/main.go +++ b/main.go @@ -1,58 +1,58 @@ package main import ( - "flag" - "fmt" - "log" - "os" + "flag" + "fmt" + "log" + "os" - "github.com/nirmata/kube-policy/controller" - "github.com/nirmata/kube-policy/server" + "github.com/nirmata/kube-policy/controller" + "github.com/nirmata/kube-policy/server" - "k8s.io/sample-controller/pkg/signals" + "k8s.io/sample-controller/pkg/signals" ) var ( - masterURL string - kubeconfig string - cert string - key string + masterURL string + kubeconfig string + cert string + key string ) func main() { - flag.Parse() + flag.Parse() - if cert == "" || key == "" { - log.Fatal("TLS certificate or/and key is not set") - } + if cert == "" || key == "" { + log.Fatal("TLS certificate or/and key is not set") + } - crdcLogger := log.New(os.Stdout, "Policy Controller: ", log.LstdFlags|log.Lshortfile) - controller, err := controller.NewPolicyController(masterURL, kubeconfig, crdcLogger) - if err != nil { - fmt.Printf("Error creating PolicyController! Error: %s\n", err) - return - } + crdcLogger := log.New(os.Stdout, "Policy Controller: ", log.LstdFlags|log.Lshortfile) + controller, err := controller.NewPolicyController(masterURL, kubeconfig, crdcLogger) + if err != nil { + fmt.Printf("Error creating PolicyController! Error: %s\n", err) + return + } - httpLogger := log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile) - server := server.NewWebhookServer(cert, key, controller, httpLogger) - server.RunAsync() + httpLogger := log.New(os.Stdout, "HTTPS Server: ", log.LstdFlags|log.Lshortfile) + server := server.NewWebhookServer(cert, key, controller, httpLogger) + server.RunAsync() - stopCh := signals.SetupSignalHandler() - controller.Run(stopCh) + stopCh := signals.SetupSignalHandler() + controller.Run(stopCh) - if err != nil { - fmt.Printf("Error running PolicyController! Error: %s\n", err) - } + if err != nil { + fmt.Printf("Error running PolicyController! Error: %s\n", err) + } - fmt.Println("Policy Controller has started") - <-stopCh - server.Stop() - fmt.Println("Policy Controller has stopped") + fmt.Println("Policy Controller has started") + <-stopCh + server.Stop() + fmt.Println("Policy Controller has stopped") } func init() { - flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") - flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") - flag.StringVar(&cert, "cert", "", "TLS certificate used in connection with cluster.") - flag.StringVar(&key, "key", "", "Key, used in TLS connection.") + flag.StringVar(&kubeconfig, "kubeconfig", "", "Path to a kubeconfig. Only required if out-of-cluster.") + flag.StringVar(&masterURL, "master", "", "The address of the Kubernetes API server. Overrides any value in kubeconfig. Only required if out-of-cluster.") + flag.StringVar(&cert, "cert", "", "TLS certificate used in connection with cluster.") + flag.StringVar(&key, "key", "", "Key, used in TLS connection.") } diff --git a/pkg/apis/policy/register.go b/pkg/apis/policy/register.go index a3301eb74f..9e9831538d 100644 --- a/pkg/apis/policy/register.go +++ b/pkg/apis/policy/register.go @@ -1,5 +1,5 @@ package policy const ( - GroupName = "policy.nirmata.io" + GroupName = "policy.nirmata.io" ) diff --git a/pkg/apis/policy/v1alpha1/register.go b/pkg/apis/policy/v1alpha1/register.go index dd21eacbfc..cd8c355461 100644 --- a/pkg/apis/policy/v1alpha1/register.go +++ b/pkg/apis/policy/v1alpha1/register.go @@ -4,7 +4,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - + "github.com/nirmata/kube-policy/pkg/apis/policy" ) diff --git a/pkg/apis/policy/v1alpha1/types.go b/pkg/apis/policy/v1alpha1/types.go index 36e18b0275..f3cfccf29b 100644 --- a/pkg/apis/policy/v1alpha1/types.go +++ b/pkg/apis/policy/v1alpha1/types.go @@ -1,7 +1,7 @@ package v1alpha1 import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // +genclient @@ -9,63 +9,63 @@ import ( // Policy is a specification for a Policy resource type Policy struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - Spec PolicySpec `json:"spec"` - Status PolicyStatus `json:"status"` + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + Spec PolicySpec `json:"spec"` + Status PolicyStatus `json:"status"` } // PolicySpec is the spec for a Policy resource type PolicySpec struct { - FailurePolicy *string `json:"failurePolicy"` - Rules []PolicyRule `json:"rules"` + FailurePolicy *string `json:"failurePolicy"` + Rules []PolicyRule `json:"rules"` } // PolicyRule is policy rule that will be applied to resource type PolicyRule struct { - Resource PolicyResource `json:"resource"` - Patches []PolicyPatch `json:"patch,omitempty"` - ConfigMapGenerator *PolicyConfigGenerator `json:"configMapGenerator,omitempty"` - SecretGenerator *PolicyConfigGenerator `json:"secretGenerator,omitempty"` + Resource PolicyResource `json:"resource"` + Patches []PolicyPatch `json:"patch,omitempty"` + ConfigMapGenerator *PolicyConfigGenerator `json:"configMapGenerator,omitempty"` + SecretGenerator *PolicyConfigGenerator `json:"secretGenerator,omitempty"` } // PolicyResource describes the resource rule applied to type PolicyResource struct { - Kind string `json:"kind"` - Name *string `json:"name"` - Selector metav1.LabelSelector `json:"selector,omitempty"` + Kind string `json:"kind"` + Name *string `json:"name"` + Selector *metav1.LabelSelector `json:"selector,omitempty"` } // PolicyPatch is TODO type PolicyPatch struct { - Path string `json:"path"` - Operation string `json:"op"` - Value string `json:"value"` + Path string `json:"path"` + Operation string `json:"op"` + Value string `json:"value"` } // PolicyConfigGenerator is TODO type PolicyConfigGenerator struct { - Name string `json:"name"` - CopyFrom *PolicyCopyFrom `json:"copyFrom"` - Data map[string]string `json:"data"` + Name string `json:"name"` + CopyFrom *PolicyCopyFrom `json:"copyFrom"` + Data map[string]string `json:"data"` } // PolicyCopyFrom is TODO type PolicyCopyFrom struct { - Namespace string `json:"namespace"` - Name string `json:"name"` + Namespace string `json:"namespace"` + Name string `json:"name"` } // PolicyStatus is the status for a Policy resource type PolicyStatus struct { - Logs []string `json:"log"` + Logs []string `json:"log"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // PolicyList is a list of Policy resources type PolicyList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata"` - Items []Policy `json:"items"` + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []Policy `json:"items"` } diff --git a/server/server.go b/server/server.go index 3eb371994c..cb814c0c5e 100644 --- a/server/server.go +++ b/server/server.go @@ -1,158 +1,158 @@ package server import ( - "context" - "crypto/tls" - "encoding/json" - "fmt" - "io/ioutil" - "log" - "net/http" - "os" - "time" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io/ioutil" + "log" + "net/http" + "os" + "time" - controller "github.com/nirmata/kube-policy/controller" - webhooks "github.com/nirmata/kube-policy/webhooks" - v1beta1 "k8s.io/api/admission/v1beta1" + controller "github.com/nirmata/kube-policy/controller" + webhooks "github.com/nirmata/kube-policy/webhooks" + v1beta1 "k8s.io/api/admission/v1beta1" ) // WebhookServer is a struct that describes // TLS server with mutation webhook type WebhookServer struct { - server http.Server - logger *log.Logger - policyController *controller.PolicyController - mutationWebhook *webhooks.MutationWebhook + server http.Server + logger *log.Logger + policyController *controller.PolicyController + mutationWebhook *webhooks.MutationWebhook } // NewWebhookServer creates new instance of WebhookServer and configures it func NewWebhookServer(certFile string, keyFile string, controller *controller.PolicyController, logger *log.Logger) *WebhookServer { - if logger == nil { - logger = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile) - } - if controller == nil { - logger.Fatal("Controller is not specified for webhook server") - } + if logger == nil { + logger = log.New(os.Stdout, "", log.LstdFlags|log.Lshortfile) + } + if controller == nil { + logger.Fatal("Controller is not specified for webhook server") + } - var config tls.Config - pair, err := tls.LoadX509KeyPair(certFile, keyFile) - if err != nil { - logger.Fatal("Unable to load certificate and key: ", err) - } - config.Certificates = []tls.Certificate{pair} + var config tls.Config + pair, err := tls.LoadX509KeyPair(certFile, keyFile) + if err != nil { + logger.Fatal("Unable to load certificate and key: ", err) + } + config.Certificates = []tls.Certificate{pair} - mw, err := webhooks.NewMutationWebhook(logger) - if err != nil { - logger.Fatal("Unable to create mutation webhook: ", err) - } + mw, err := webhooks.NewMutationWebhook(logger) + if err != nil { + logger.Fatal("Unable to create mutation webhook: ", err) + } - ws := &WebhookServer{ - logger: logger, - policyController: controller, - mutationWebhook: mw, - } + ws := &WebhookServer{ + logger: logger, + policyController: controller, + mutationWebhook: mw, + } - mux := http.NewServeMux() - mux.HandleFunc("/mutate", ws.serve) + mux := http.NewServeMux() + mux.HandleFunc("/mutate", ws.serve) - ws.server = http.Server{ - Addr: ":443", // Listen on port for HTTPS requests - TLSConfig: &config, - Handler: mux, - ErrorLog: logger, - ReadTimeout: 15 * time.Second, - WriteTimeout: 15 * time.Second, - } + ws.server = http.Server{ + Addr: ":443", // Listen on port for HTTPS requests + TLSConfig: &config, + Handler: mux, + ErrorLog: logger, + ReadTimeout: 15 * time.Second, + WriteTimeout: 15 * time.Second, + } - return ws + return ws } func (ws *WebhookServer) serve(w http.ResponseWriter, r *http.Request) { - if r.URL.Path == "/mutate" { - admissionReview := ws.parseAdmissionReview(r, w) - if admissionReview == nil { - return - } + if r.URL.Path == "/mutate" { + admissionReview := ws.parseAdmissionReview(r, w) + if admissionReview == nil { + return + } - var admissionResponse *v1beta1.AdmissionResponse - if webhooks.AdmissionIsRequired(admissionReview.Request) { - admissionResponse = ws.mutationWebhook.Mutate(admissionReview.Request, ws.policyController.GetPolicies()) - } + var admissionResponse *v1beta1.AdmissionResponse + if webhooks.AdmissionIsRequired(admissionReview.Request) { + admissionResponse = ws.mutationWebhook.Mutate(admissionReview.Request, ws.policyController.GetPolicies()) + } - if admissionResponse == nil { - admissionResponse = &v1beta1.AdmissionResponse{ - Allowed: true, - } - } + if admissionResponse == nil { + admissionResponse = &v1beta1.AdmissionResponse{ + Allowed: true, + } + } - admissionReview.Response = admissionResponse - admissionReview.Response.UID = admissionReview.Request.UID + admissionReview.Response = admissionResponse + admissionReview.Response.UID = admissionReview.Request.UID - responseJson, err := json.Marshal(admissionReview) - if err != nil { - http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError) - return - } + responseJson, err := json.Marshal(admissionReview) + if err != nil { + http.Error(w, fmt.Sprintf("Could not encode response: %v", err), http.StatusInternalServerError) + return + } - ws.logger.Printf("Response body\n:%v", string(responseJson)) - w.Header().Set("Content-Type", "application/json; charset=utf-8") - if _, err := w.Write(responseJson); err != nil { - http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) - } - } else { - http.Error(w, fmt.Sprintf("Unexpected method path: %v", r.URL.Path), http.StatusNotFound) - } + ws.logger.Printf("Response body\n:%v", string(responseJson)) + w.Header().Set("Content-Type", "application/json; charset=utf-8") + if _, err := w.Write(responseJson); err != nil { + http.Error(w, fmt.Sprintf("could not write response: %v", err), http.StatusInternalServerError) + } + } else { + http.Error(w, fmt.Sprintf("Unexpected method path: %v", r.URL.Path), http.StatusNotFound) + } } // Answers to the http.ResponseWriter if request is not valid func (ws *WebhookServer) parseAdmissionReview(request *http.Request, writer http.ResponseWriter) *v1beta1.AdmissionReview { - var body []byte - if request.Body != nil { - if data, err := ioutil.ReadAll(request.Body); err == nil { - body = data - } - } - if len(body) == 0 { - ws.logger.Print("Error: empty body") - http.Error(writer, "empty body", http.StatusBadRequest) - return nil - } + var body []byte + if request.Body != nil { + if data, err := ioutil.ReadAll(request.Body); err == nil { + body = data + } + } + if len(body) == 0 { + ws.logger.Print("Error: empty body") + http.Error(writer, "empty body", http.StatusBadRequest) + return nil + } - contentType := request.Header.Get("Content-Type") - if contentType != "application/json" { - ws.logger.Printf("Error: invalid Content-Type: %v", contentType) - http.Error(writer, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType) - return nil - } + contentType := request.Header.Get("Content-Type") + if contentType != "application/json" { + ws.logger.Printf("Error: invalid Content-Type: %v", contentType) + http.Error(writer, "invalid Content-Type, expect `application/json`", http.StatusUnsupportedMediaType) + return nil + } - admissionReview := &v1beta1.AdmissionReview{} - if err := json.Unmarshal(body, &admissionReview); err != nil { - ws.logger.Printf("Error: Can't decode body as AdmissionReview: %v", err) - http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed) - return nil - } else { - ws.logger.Printf("Request body:\n%v", string(body)) - return admissionReview - } + admissionReview := &v1beta1.AdmissionReview{} + if err := json.Unmarshal(body, &admissionReview); err != nil { + ws.logger.Printf("Error: Can't decode body as AdmissionReview: %v", err) + http.Error(writer, "Can't decode body as AdmissionReview", http.StatusExpectationFailed) + return nil + } else { + ws.logger.Printf("Request body:\n%v", string(body)) + return admissionReview + } } // RunAsync runs TLS server in separate // thread and returns control immediately func (ws *WebhookServer) RunAsync() { - go func(ws *WebhookServer) { - err := ws.server.ListenAndServeTLS("", "") - if err != nil { - ws.logger.Fatal(err) - } - }(ws) + go func(ws *WebhookServer) { + err := ws.server.ListenAndServeTLS("", "") + if err != nil { + ws.logger.Fatal(err) + } + }(ws) } // Stop stops TLS server func (ws *WebhookServer) Stop() { - err := ws.server.Shutdown(context.Background()) - if err != nil { - // Error from closing listeners, or context timeout: - ws.logger.Printf("Server Shutdown error: %v", err) - ws.server.Close() - } + err := ws.server.Shutdown(context.Background()) + if err != nil { + // Error from closing listeners, or context timeout: + ws.logger.Printf("Server Shutdown error: %v", err) + ws.server.Close() + } } diff --git a/webhooks/admission.go b/webhooks/admission.go index 262cb9de6b..ff1f36138e 100644 --- a/webhooks/admission.go +++ b/webhooks/admission.go @@ -1,109 +1,110 @@ package webhooks import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - "k8s.io/apimachinery/pkg/labels" - "k8s.io/api/admission/v1beta1" - "encoding/json" + "encoding/json" + + types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + "k8s.io/api/admission/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" ) var supportedKinds = [...]string{ - "ConfigMap", - "CronJob", - "DaemonSet", - "Deployment", - "Endpoint", - "HorizontalPodAutoscaler", - "Ingress", - "Job", - "LimitRange", - "Namespace", - "NetworkPolicy", - "PersistentVolumeClaim", - "PodDisruptionBudget", - "PodTemplate", - "ResourceQuota", - "Secret", - "Service", - "StatefulSet", + "ConfigMap", + "CronJob", + "DaemonSet", + "Deployment", + "Endpoint", + "HorizontalPodAutoscaler", + "Ingress", + "Job", + "LimitRange", + "Namespace", + "NetworkPolicy", + "PersistentVolumeClaim", + "PodDisruptionBudget", + "PodTemplate", + "ResourceQuota", + "Secret", + "Service", + "StatefulSet", } func kindIsSupported(kind string) bool { - for _, k := range supportedKinds { - if k == kind { - return true - } - } - return false + for _, k := range supportedKinds { + if k == kind { + return true + } + } + return false } // AdmissionIsRequired checks for admission if kind is supported func AdmissionIsRequired(request *v1beta1.AdmissionRequest) bool { - // Here you can make additional hardcoded checks - return kindIsSupported(request.Kind.Kind) + // Here you can make additional hardcoded checks + return kindIsSupported(request.Kind.Kind) } // IsRuleApplicableToRequest checks requests kind, name and labels to fit the policy func IsRuleApplicableToRequest(policyResource types.PolicyResource, request *v1beta1.AdmissionRequest) bool { - if policyResource.Selector == nil && policyResource.Name == nil { - // TODO: selector or name MUST be specified - return false - } - - if policyResource.Kind != request.Kind.Kind { - return false - } + if policyResource.Selector == nil && policyResource.Name == nil { + // TODO: selector or name MUST be specified + return false + } + + if policyResource.Kind != request.Kind.Kind { + return false + } if request.Object.Raw != nil { - meta := parseMetadataFromObject(request.Object.Raw) - name := parseNameFromMetadata(meta) + meta := parseMetadataFromObject(request.Object.Raw) + name := parseNameFromMetadata(meta) - if (policyResource.Name != nil && *policyResource.Name != name) { - return false - } - - if policyResource.Selector != nil { - selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector) + if policyResource.Name != nil && *policyResource.Name != name { + return false + } - if err != nil { - // TODO: log that selector is invalid - return false - } - - labelMap := parseLabelsFromMetadata(meta) - - if !selector.Matches(labelMap) { - return false - } - } - } + if policyResource.Selector != nil { + selector, err := metav1.LabelSelectorAsSelector(policyResource.Selector) - return true + if err != nil { + // TODO: log that selector is invalid + return false + } + + labelMap := parseLabelsFromMetadata(meta) + + if !selector.Matches(labelMap) { + return false + } + } + } + + return true } func parseMetadataFromObject(bytes []byte) map[string]interface{} { - var objectJSON map[string]interface{} - json.Unmarshal(bytes, &objectJSON) - - return objectJSON["metadata"].(map[string]interface{}) + var objectJSON map[string]interface{} + json.Unmarshal(bytes, &objectJSON) + + return objectJSON["metadata"].(map[string]interface{}) } func parseLabelsFromMetadata(meta map[string]interface{}) labels.Set { - if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok { - labelMap := make(labels.Set, len(interfaceMap)) + if interfaceMap, ok := meta["labels"].(map[string]interface{}); ok { + labelMap := make(labels.Set, len(interfaceMap)) - for key, value := range interfaceMap { - labelMap[key] = value.(string) - } - return labelMap - } - return nil + for key, value := range interfaceMap { + labelMap[key] = value.(string) + } + return labelMap + } + return nil } func parseNameFromMetadata(meta map[string]interface{}) string { if name, ok := meta["name"].(string); ok { - return name - } - return "" -} \ No newline at end of file + return name + } + return "" +} diff --git a/webhooks/admission_test.go b/webhooks/admission_test.go index 226083297f..3bccc69f56 100644 --- a/webhooks/admission_test.go +++ b/webhooks/admission_test.go @@ -1,133 +1,224 @@ package webhooks_test import ( - "testing" + "testing" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - "github.com/nirmata/kube-policy/webhooks" - v1beta1 "k8s.io/api/admission/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + "github.com/nirmata/kube-policy/webhooks" + v1beta1 "k8s.io/api/admission/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func TestAdmissionIsRequired(t *testing.T) { - var request v1beta1.AdmissionRequest - request.Kind.Kind = "ConfigMap" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "CronJob" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "DaemonSet" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "Deployment" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "Endpoint" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "HorizontalPodAutoscaler" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "Ingress" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "Job" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "LimitRange" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "Namespace" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "NetworkPolicy" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "PersistentVolumeClaim" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "PodDisruptionBudget" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "PodTemplate" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "ResourceQuota" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "Secret" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "Service" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) - request.Kind.Kind = "StatefulSet" - assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + var request v1beta1.AdmissionRequest + request.Kind.Kind = "ConfigMap" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "CronJob" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "DaemonSet" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "Deployment" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "Endpoint" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "HorizontalPodAutoscaler" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "Ingress" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "Job" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "LimitRange" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "Namespace" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "NetworkPolicy" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "PersistentVolumeClaim" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "PodDisruptionBudget" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "PodTemplate" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "ResourceQuota" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "Secret" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "Service" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) + request.Kind.Kind = "StatefulSet" + assertEq(t, true, webhooks.AdmissionIsRequired(&request)) } func TestIsRuleResourceFitsRequest_Kind(t *testing.T) { - resourceName := "test-config-map" - resource := types.PolicyResource { - Kind: "ConfigMap", - Name: &resourceName, - } - request := v1beta1.AdmissionRequest { - Kind: metav1.GroupVersionKind{ Kind: "ConfigMap" }, - } + resourceName := "test-config-map" + resource := types.PolicyResource{ + Kind: "ConfigMap", + Name: &resourceName, + } + request := v1beta1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, + } - objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) - request.Object.Raw = objectByteArray + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray - assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) - resource.Kind = "Deployment" - assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + resource.Kind = "Deployment" + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) } func TestIsRuleResourceFitsRequest_Name(t *testing.T) { - resourceName := "test-config-map" - resource := types.PolicyResource { - Kind: "ConfigMap", - Name: &resourceName, - } - request := v1beta1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, - } + resourceName := "test-config-map" + resource := types.PolicyResource{ + Kind: "ConfigMap", + Name: &resourceName, + } + request := v1beta1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, + } - objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) - request.Object.Raw = objectByteArray - assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) - resourceName = "test-config-map-new" - assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + resourceName = "test-config-map-new" + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) - objectByteArray = []byte(`{"metadata":{"name":"test-config-map-new","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) - request.Object.Raw = objectByteArray - assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + objectByteArray = []byte(`{"metadata":{"name":"test-config-map-new","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) - objectByteArray = []byte(`{"metadata":{"name":"","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) - request.Object.Raw = objectByteArray - assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) + objectByteArray = []byte(`{"metadata":{"name":"","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) } -func TestIsRuleResourceFitsRequest_Selector(t *testing.T) { - resource := types.PolicyResource { - Kind: "ConfigMap", - Selector: &metav1.LabelSelector { - MatchLabels: map[string]string { - "label1" : "test1", - "label2" : "test2", - }, - MatchExpressions: nil, - }, - } +func TestIsRuleResourceFitsRequest_MatchExpressions(t *testing.T) { + request := v1beta1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, + } - request := v1beta1.AdmissionRequest{ - Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, - } + resource := types.PolicyResource{ + Kind: "ConfigMap", + Selector: &metav1.LabelSelector{ + MatchLabels: nil, + MatchExpressions: []metav1.LabelSelectorRequirement{ + metav1.LabelSelectorRequirement{ + Key: "label2", + Operator: "NotIn", + Values: []string{ + "sometest1", + }, + }, + metav1.LabelSelectorRequirement{ + Key: "label1", + Operator: "In", + Values: []string{ + "test1", + "test8", + "test201", + }, + }, + metav1.LabelSelectorRequirement{ + Key: "label3", + Operator: "DoesNotExist", + Values: nil, + }, + }, + }, + } - objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) - request.Object.Raw = objectByteArray - assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray - objectByteArray = []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label3":"test1","label2":"test2"}}}`) - request.Object.Raw = objectByteArray - assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) +} - resource = types.PolicyResource { - Kind: "ConfigMap", - Selector: &metav1.LabelSelector { - MatchLabels: map[string]string { - "label3" : "test1", - "label2" : "test2", - }, - MatchExpressions: nil, - }, - } +func TestIsRuleResourceFitsRequest_MatchLabels(t *testing.T) { + resource := types.PolicyResource{ + Kind: "ConfigMap", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label1": "test1", + "label2": "test2", + }, + MatchExpressions: nil, + }, + } - assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + request := v1beta1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, + } - // TODO: MatchExpressions tests should be done -} \ No newline at end of file + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + + objectByteArray = []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label3":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + assertEq(t, false, webhooks.IsRuleApplicableToRequest(resource, &request)) + + resource = types.PolicyResource{ + Kind: "ConfigMap", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label3": "test1", + "label2": "test2", + }, + MatchExpressions: nil, + }, + } + + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) +} + +func TestIsRuleResourceFitsRequest_MatchLabelsAndMatchExpressions(t *testing.T) { + request := v1beta1.AdmissionRequest{ + Kind: metav1.GroupVersionKind{Kind: "ConfigMap"}, + } + + resource := types.PolicyResource{ + Kind: "ConfigMap", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label1": "test1", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + metav1.LabelSelectorRequirement{ + Key: "label2", + Operator: "In", + Values: []string{ + "test2", + }, + }, + }, + }, + } + + objectByteArray := []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) + + resource = types.PolicyResource{ + Kind: "ConfigMap", + Selector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label1": "test1", + }, + MatchExpressions: []metav1.LabelSelectorRequirement{ + metav1.LabelSelectorRequirement{ + Key: "label2", + Operator: "NotIn", + Values: []string{ + "sometest1", + }, + }, + }, + }, + } + + objectByteArray = []byte(`{"metadata":{"name":"test-config-map","namespace":"default","creationTimestamp":null,"labels":{"label1":"test1","label2":"test2"}}}`) + request.Object.Raw = objectByteArray + + assertEq(t, true, webhooks.IsRuleApplicableToRequest(resource, &request)) +} diff --git a/webhooks/mutation.go b/webhooks/mutation.go index 1fd415a7fe..ce43642f91 100644 --- a/webhooks/mutation.go +++ b/webhooks/mutation.go @@ -1,126 +1,132 @@ package webhooks import ( - "encoding/json" - "errors" - "log" + "encoding/json" + "errors" + "log" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" - v1beta1 "k8s.io/api/admission/v1beta1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + v1beta1 "k8s.io/api/admission/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) +// MutationWebhook is a data type that represents +// buisness logic for resource mutation type MutationWebhook struct { - logger *log.Logger + logger *log.Logger } +// NewMutationWebhook is a method that returns new instance +// of MutationWebhook struct func NewMutationWebhook(logger *log.Logger) (*MutationWebhook, error) { - if logger == nil { - return nil, errors.New("Logger must be set for the mutation webhook") - } - return &MutationWebhook{logger: logger}, nil + if logger == nil { + return nil, errors.New("Logger must be set for the mutation webhook") + } + return &MutationWebhook{logger: logger}, nil } +// Mutate applies admission to request func (mw *MutationWebhook) Mutate(request *v1beta1.AdmissionRequest, policies []types.Policy) *v1beta1.AdmissionResponse { - mw.logger.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v patchOperation=%v UserInfo=%v", - request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, request.UserInfo) + mw.logger.Printf("AdmissionReview for Kind=%v, Namespace=%v Name=%v UID=%v patchOperation=%v UserInfo=%v", + request.Kind.Kind, request.Namespace, request.Name, request.UID, request.Operation, request.UserInfo) - if len(policies) == 0 { - return nil - } + if len(policies) == 0 { + return nil + } - var allPatches []types.PolicyPatch - for _, policy := range policies { - var stopOnError bool = true - if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" { - stopOnError = false - } + var allPatches []types.PolicyPatch + for _, policy := range policies { + stopOnError := true + if policy.Spec.FailurePolicy != nil && *policy.Spec.FailurePolicy == "continueOnError" { + stopOnError = false + } - for ruleIdx, rule := range policy.Spec.Rules { - if IsRuleApplicableToRequest(rule.Resource, request) { - mw.logger.Printf("Applying policy %v, rule index = %v", policy.ObjectMeta.Name, ruleIdx) - rulePatches, err := mw.applyPolicyRule(request, rule) - /* - * If at least one error is detected in the rule, the entire rule will not be applied. - * This may be changed in the future by varying the policy.Spec.FailurePolicy values. - */ - if err != nil { - mw.logger.Printf("Error occurred while applying the policy: %v", err) - if stopOnError { - mw.logger.Printf("/!\\ Denying the request according to FailurePolicy spec /!\\") - return errorToResponse(err, false) - } - } else { - mw.logger.Printf("Prepared %v patches", len(rulePatches)) - allPatches = append(allPatches, rulePatches...) - } - } - } - } + for ruleIdx, rule := range policy.Spec.Rules { + if IsRuleApplicableToRequest(rule.Resource, request) { + mw.logger.Printf("Applying policy %v, rule index = %v", policy.ObjectMeta.Name, ruleIdx) + rulePatches, err := mw.applyPolicyRule(request, rule) + /* + * If at least one error is detected in the rule, the entire rule will not be applied. + * This may be changed in the future by varying the policy.Spec.FailurePolicy values. + */ + if err != nil { + mw.logger.Printf("Error occurred while applying the policy: %v", err) + if stopOnError { + mw.logger.Printf("/!\\ Denying the request according to FailurePolicy spec /!\\") + return errorToResponse(err, false) + } + } else { + mw.logger.Printf("Prepared %v patches", len(rulePatches)) + allPatches = append(allPatches, rulePatches...) + } + } + } + } - patchesBytes, err := SerializePatches(allPatches) - if err != nil { - mw.logger.Printf("Error occerred while serializing JSONPathch: %v", err) - return errorToResponse(err, true) - } + patchesBytes, err := SerializePatches(allPatches) + if err != nil { + mw.logger.Printf("Error occerred while serializing JSONPathch: %v", err) + return errorToResponse(err, true) + } - return &v1beta1.AdmissionResponse{ - Allowed: true, - Patch: patchesBytes, - PatchType: func() *v1beta1.PatchType { - pt := v1beta1.PatchTypeJSONPatch - return &pt - }(), - } + return &v1beta1.AdmissionResponse{ + Allowed: true, + Patch: patchesBytes, + PatchType: func() *v1beta1.PatchType { + pt := v1beta1.PatchTypeJSONPatch + return &pt + }(), + } } // Applies all possible patches in a rule func (mw *MutationWebhook) applyPolicyRule(request *v1beta1.AdmissionRequest, rule types.PolicyRule) ([]types.PolicyPatch, error) { - var allPatches []types.PolicyPatch - if rule.Patches == nil && rule.ConfigMapGenerator == nil && rule.SecretGenerator == nil { - return nil, errors.New("The rule is empty!") - } + var allPatches []types.PolicyPatch + if rule.Patches == nil && rule.ConfigMapGenerator == nil && rule.SecretGenerator == nil { + return nil, errors.New("The rule is empty") + } - allPatches = append(allPatches, rule.Patches...) + allPatches = append(allPatches, rule.Patches...) - if rule.ConfigMapGenerator != nil { - // TODO: Make patches from configMapGenerator and add them to returned array - } + if rule.ConfigMapGenerator != nil { + // TODO: Make patches from configMapGenerator and add them to returned array + } - if rule.SecretGenerator != nil { - // TODO: Make patches from secretGenerator and add them to returned array - } + if rule.SecretGenerator != nil { + // TODO: Make patches from secretGenerator and add them to returned array + } - return allPatches, nil + return allPatches, nil } +// SerializePatches converts JSON patches to byte array func SerializePatches(patches []types.PolicyPatch) ([]byte, error) { - var result []byte - result = append(result, []byte("[\n")...) - for index, patch := range patches { - if patch.Operation == "" || patch.Path == "" { - return nil, errors.New("JSONPatch doesn't contain mandatory fields 'path' or 'op'") - } + var result []byte + result = append(result, []byte("[\n")...) + for index, patch := range patches { + if patch.Operation == "" || patch.Path == "" { + return nil, errors.New("JSONPatch doesn't contain mandatory fields 'path' or 'op'") + } - patchBytes, err := json.Marshal(patch) - if err != nil { - return nil, err - } + patchBytes, err := json.Marshal(patch) + if err != nil { + return nil, err + } - result = append(result, patchBytes...) - if index != (len(patches) - 1) { - result = append(result, []byte(",\n")...) - } - } - result = append(result, []byte("\n]")...) - return result, nil + result = append(result, patchBytes...) + if index != (len(patches) - 1) { + result = append(result, []byte(",\n")...) + } + } + result = append(result, []byte("\n]")...) + return result, nil } func errorToResponse(err error, allowed bool) *v1beta1.AdmissionResponse { - return &v1beta1.AdmissionResponse{ - Result: &metav1.Status{ - Message: err.Error(), - }, - Allowed: allowed, - } + return &v1beta1.AdmissionResponse{ + Result: &metav1.Status{ + Message: err.Error(), + }, + Allowed: allowed, + } } diff --git a/webhooks/mutation_test.go b/webhooks/mutation_test.go index 06ddb54787..1d8a0efe11 100644 --- a/webhooks/mutation_test.go +++ b/webhooks/mutation_test.go @@ -1,48 +1,48 @@ package webhooks_test import ( - "testing" + "testing" - "github.com/nirmata/kube-policy/webhooks" + "github.com/nirmata/kube-policy/webhooks" - //v1beta1 "k8s.io/api/admission/v1beta1" - //metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" + //v1beta1 "k8s.io/api/admission/v1beta1" + //metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "github.com/nirmata/kube-policy/pkg/apis/policy/v1alpha1" ) func TestSerializePatches_Empty(t *testing.T) { - var patches []types.PolicyPatch - bytes, err := webhooks.SerializePatches(patches) - assertEq(t, nil, err) - assertEqStringAndData(t, "[\n\n]", bytes) + var patches []types.PolicyPatch + bytes, err := webhooks.SerializePatches(patches) + assertEq(t, nil, err) + assertEqStringAndData(t, "[\n\n]", bytes) } func TestSerializePatches_SingleValid(t *testing.T) { - patch := types.PolicyPatch{ - Path: "/metadata/labels/is-mutated", - Operation: "add", - Value: "true", - } - patches := []types.PolicyPatch{patch} - bytes, err := webhooks.SerializePatches(patches) - assertEq(t, nil, err) - assertEqStringAndData(t, `[ + patch := types.PolicyPatch{ + Path: "/metadata/labels/is-mutated", + Operation: "add", + Value: "true", + } + patches := []types.PolicyPatch{patch} + bytes, err := webhooks.SerializePatches(patches) + assertEq(t, nil, err) + assertEqStringAndData(t, `[ {"path":"/metadata/labels/is-mutated","op":"add","value":"true"} ]`, bytes) } func TestSerializePatches_SingleInvalid(t *testing.T) { - patch := types.PolicyPatch{ - Path: "/metadata/labels/is-mutated", - Value: "true", - } - patches := []types.PolicyPatch{patch} - _, err := webhooks.SerializePatches(patches) - assertNe(t, nil, err) - patches[0].Path = "" - patches[0].Operation = "delete" - _, err = webhooks.SerializePatches(patches) - assertNe(t, nil, err) + patch := types.PolicyPatch{ + Path: "/metadata/labels/is-mutated", + Value: "true", + } + patches := []types.PolicyPatch{patch} + _, err := webhooks.SerializePatches(patches) + assertNe(t, nil, err) + patches[0].Path = "" + patches[0].Operation = "delete" + _, err = webhooks.SerializePatches(patches) + assertNe(t, nil, err) } // patch := `[ {"op":"add","path":"/metadata/labels","value":{"is-mutated":"true"}} ]` diff --git a/webhooks/utils_test.go b/webhooks/utils_test.go index 4b03350431..2ad348d3a2 100644 --- a/webhooks/utils_test.go +++ b/webhooks/utils_test.go @@ -1,38 +1,38 @@ package webhooks_test import ( - "testing" + "testing" ) func assertEq(t *testing.T, expected interface{}, actual interface{}) { - if expected != actual { - t.Errorf("%s != %s", expected, actual) - } + if expected != actual { + t.Errorf("%s != %s", expected, actual) + } } func assertNe(t *testing.T, expected interface{}, actual interface{}) { - if expected == actual { - t.Errorf("%s != %s", expected, actual) - } + if expected == actual { + t.Errorf("%s != %s", expected, actual) + } } func assertEqDataImpl(t *testing.T, expected, actual []byte, formatModifier string) { - if len(expected) != len(actual) { - t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual) - return - } + if len(expected) != len(actual) { + t.Errorf("len(expected) != len(actual): %d != %d\n1:"+formatModifier+"\n2:"+formatModifier, len(expected), len(actual), expected, actual) + return + } - for idx, val := range actual { - if val != expected[idx] { - t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual) - } - } + for idx, val := range actual { + if val != expected[idx] { + t.Errorf("Slices not equal at index %d:\n1:"+formatModifier+"\n2:"+formatModifier, idx, expected, actual) + } + } } func assertEqData(t *testing.T, expected, actual []byte) { - assertEqDataImpl(t, expected, actual, "%x") + assertEqDataImpl(t, expected, actual, "%x") } func assertEqStringAndData(t *testing.T, str string, data []byte) { - assertEqDataImpl(t, []byte(str), data, "%s") + assertEqDataImpl(t, []byte(str), data, "%s") }