1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

NK-22: Fixed build error with Selector pointer. Added comments. Changed tab to 4 spaces identation. Added unit tests for LabelSelector.

This commit is contained in:
belyshevdenis 2019-03-01 14:16:20 +02:00
parent d0de77f9ce
commit d593fe1a92
13 changed files with 669 additions and 571 deletions

View file

@ -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 ""
}

View file

@ -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

View file

@ -1,4 +1,4 @@
apiVersion: nirmata.io/v1alpha1
apiVersion: policy.nirmata.io/v1alpha1
kind : Policy
metadata:
name: selector-policy

74
main.go
View file

@ -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.")
}

View file

@ -1,5 +1,5 @@
package policy
const (
GroupName = "policy.nirmata.io"
GroupName = "policy.nirmata.io"
)

View file

@ -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"
)

View file

@ -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"`
}

View file

@ -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()
}
}

View file

@ -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 ""
}
return name
}
return ""
}

View file

@ -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
}
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))
}

View file

@ -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,
}
}

View file

@ -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"}} ]`

View file

@ -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")
}