From aca4b13d5416e0ffd8b6ddc070862f740ae7c6a6 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani Date: Wed, 14 Aug 2019 15:06:38 -0700 Subject: [PATCH 1/2] add resource manager to namespace controller --- pkg/namespace/controller.go | 4 ++++ pkg/namespace/generation.go | 23 +++++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/pkg/namespace/controller.go b/pkg/namespace/controller.go index 9966ce01ba..71518d3834 100644 --- a/pkg/namespace/controller.go +++ b/pkg/namespace/controller.go @@ -83,6 +83,10 @@ func NewNamespaceController(kyvernoClient *kyvernoclient.Clientset, nsc.pvListerSynced = pInformer.Informer().HasSynced nsc.pvLister = pvInformer.Lister() + // resource manager + // rebuild after 300 seconds/ 5 mins + nsc.rm = NewResourceManager(300) + return nsc } func (nsc *NamespaceController) addNamespace(obj interface{}) { diff --git a/pkg/namespace/generation.go b/pkg/namespace/generation.go index 9851101cb8..7323c11424 100644 --- a/pkg/namespace/generation.go +++ b/pkg/namespace/generation.go @@ -69,6 +69,20 @@ func (rm *ResourceManager) ProcessResource(policy, pv, kind, ns, name, rv string return ok == false } +//Drop drop the cache after every rebuild interval mins +//TODO: or drop based on the size +func (rm *ResourceManager) Drop() { + timeSince := time.Since(rm.time) + glog.V(4).Infof("time since last cache reset time %v is %v", rm.time, timeSince) + glog.V(4).Infof("cache rebuild time %v", time.Duration(rm.rebuildTime)*time.Second) + if timeSince > time.Duration(rm.rebuildTime)*time.Second { + rm.mux.Lock() + defer rm.mux.Unlock() + rm.data = map[string]interface{}{} + rm.time = time.Now() + glog.V(4).Infof("dropping cache at time %v", rm.time) + } +} func buildKey(policy, pv, kind, ns, name, rv string) string { return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv } @@ -124,6 +138,8 @@ func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []i glog.Infof("unable to convert to unstructured, not processing any policies: %v", err) return policyInfos } + nsc.rm.Drop() + ns := unstructured.Unstructured{Object: unstr} // get all the policies that have a generate rule and apply on the namespace @@ -131,8 +147,15 @@ func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []i policies := listpolicies(ns, nsc.pLister) for _, policy := range policies { + // pre-processing, check if the policy and resource version has been processed before + if !nsc.rm.ProcessResource(policy.Name, policy.ResourceVersion, ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion()) { + glog.V(4).Infof("policy %s with resource version %s already processed on resource %s/%s/%s with resource version %s", policy.Name, policy.ResourceVersion, ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion()) + continue + } policyInfo := applyPolicy(nsc.client, ns, *policy) policyInfos = append(policyInfos, policyInfo) + // post-processing, register the resource as processed + nsc.rm.RegisterResource(policy.GetName(), policy.GetResourceVersion(), ns.GetKind(), ns.GetNamespace(), ns.GetName(), ns.GetResourceVersion()) } return policyInfos } From 63a5337c9b2ad3f7812a560ac5fc446f524132a5 Mon Sep 17 00:00:00 2001 From: shivkumar dudhani Date: Wed, 14 Aug 2019 18:40:33 -0700 Subject: [PATCH 2/2] generation test --- main.go | 9 +- .../kyverno/v1alpha1/expansion_generated.go | 28 ++- pkg/engine/generation.go | 5 +- pkg/namespace/controller.go | 14 +- pkg/namespace/expansion.go | 46 +++++ pkg/namespace/generation.go | 176 +----------------- pkg/policy/controller.go | 3 +- pkg/webhooks/mutation.go | 1 - pkg/webhooks/registration.go | 16 +- 9 files changed, 103 insertions(+), 195 deletions(-) create mode 100644 pkg/namespace/expansion.go diff --git a/main.go b/main.go index cef58b4080..b829fee827 100644 --- a/main.go +++ b/main.go @@ -2,6 +2,7 @@ package main import ( "flag" + "time" "github.com/golang/glog" clientNew "github.com/nirmata/kyverno/pkg/clientNew/clientset/versioned" @@ -53,8 +54,8 @@ func main() { // watches CRD resources: // - Policy // - PolicyVolation - // - cache resync time: 30 seconds - pInformer := kyvernoinformer.NewSharedInformerFactoryWithOptions(pclient, 30) + // - cache resync time: 10 seconds + pInformer := kyvernoinformer.NewSharedInformerFactoryWithOptions(pclient, 10*time.Second) // EVENT GENERATOR // - generate event with retry egen := event.NewEventGenerator(client, pInformer.Kyverno().V1alpha1().Policies()) @@ -78,12 +79,12 @@ func main() { // NAMESPACE INFORMER // watches namespace resource - // - cache resync time: 30 seconds + // - cache resync time: 10 seconds kubeClient, err := utils.NewKubeClient(clientConfig) if err != nil { glog.Fatalf("Error creating kubernetes client: %v\n", err) } - kubeInformer := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 30) + kubeInformer := kubeinformers.NewSharedInformerFactoryWithOptions(kubeClient, 10*time.Second) // GENERATE CONTROLLER // - watches for Namespace resource and generates resource based on the policy generate rule diff --git a/pkg/clientNew/listers/kyverno/v1alpha1/expansion_generated.go b/pkg/clientNew/listers/kyverno/v1alpha1/expansion_generated.go index bfe398f1be..f37f2e4f76 100644 --- a/pkg/clientNew/listers/kyverno/v1alpha1/expansion_generated.go +++ b/pkg/clientNew/listers/kyverno/v1alpha1/expansion_generated.go @@ -22,6 +22,7 @@ import ( "fmt" kyverno "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" + v1alpha1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1alpha1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" ) @@ -30,11 +31,36 @@ import ( // PolicyLister. type PolicyListerExpansion interface { GetPolicyForPolicyViolation(pv *kyverno.PolicyViolation) ([]*kyverno.Policy, error) + ListResources(selector labels.Selector) (ret []*v1alpha1.Policy, err error) } // PolicyViolationListerExpansion allows custom methods to be added to // PolicyViolationLister. -type PolicyViolationListerExpansion interface{} +type PolicyViolationListerExpansion interface { + // List lists all PolicyViolations in the indexer with GVK. + // List lists all PolicyViolations in the indexer with GVK. + ListResources(selector labels.Selector) (ret []*v1alpha1.PolicyViolation, err error) +} + +//ListResources is a wrapper to List and adds the resource kind information +// as the lister is specific to a gvk we can harcode the values here +func (pvl *policyViolationLister) ListResources(selector labels.Selector) (ret []*v1alpha1.PolicyViolation, err error) { + policyviolations, err := pvl.List(selector) + for index := range policyviolations { + policyviolations[index].SetGroupVersionKind(kyverno.SchemeGroupVersion.WithKind("PolicyViolation")) + } + return policyviolations, nil +} + +//ListResources is a wrapper to List and adds the resource kind information +// as the lister is specific to a gvk we can harcode the values here +func (pl *policyLister) ListResources(selector labels.Selector) (ret []*v1alpha1.Policy, err error) { + policies, err := pl.List(selector) + for index := range policies { + policies[index].SetGroupVersionKind(kyverno.SchemeGroupVersion.WithKind("Policy")) + } + return policies, err +} func (pl *policyLister) GetPolicyForPolicyViolation(pv *kyverno.PolicyViolation) ([]*kyverno.Policy, error) { if len(pv.Labels) == 0 { diff --git a/pkg/engine/generation.go b/pkg/engine/generation.go index 7dbaeb107b..5577cdc768 100644 --- a/pkg/engine/generation.go +++ b/pkg/engine/generation.go @@ -93,10 +93,10 @@ func applyRuleGenerator(client *client.Client, ns unstructured.Unstructured, gen _, err = client.CreateResource(gen.Kind, ns.GetName(), resource, false) if err != nil { - glog.V(4).Infof("generate rule: unable to create resource %s/%s/%s: %v", gen.Kind, ns.GetKind(), ns.GetNamespace(), ns.GetName(), err) + glog.V(4).Infof("generate rule: unable to create resource %s/%s/%s: %v", gen.Kind, resource.GetNamespace(), resource.GetName(), err) return err } - glog.V(4).Infof("generate rule: created resource %s/%s/%s", gen.Kind, ns.GetKind(), ns.GetNamespace(), ns.GetName()) + glog.V(4).Infof("generate rule: created resource %s/%s/%s", gen.Kind, resource.GetNamespace(), resource.GetName()) return nil } @@ -125,7 +125,6 @@ func checkResource(config interface{}, resource *unstructured.Unstructured) (boo if err != nil { // unable to unmarshall return false, err - } var objData interface{} diff --git a/pkg/namespace/controller.go b/pkg/namespace/controller.go index 71518d3834..fae6b7539f 100644 --- a/pkg/namespace/controller.go +++ b/pkg/namespace/controller.go @@ -16,7 +16,6 @@ import ( v1 "k8s.io/api/core/v1" utilruntime "k8s.io/apimachinery/pkg/util/runtime" v1Informer "k8s.io/client-go/informers/core/v1" - v1CoreLister "k8s.io/client-go/listers/core/v1" "k8s.io/client-go/tools/cache" "k8s.io/client-go/util/workqueue" ) @@ -33,8 +32,10 @@ type NamespaceController struct { syncHandler func(nsKey string) error enqueueNs func(ns *v1.Namespace) + //nsLister provides expansion to the namespace lister to inject GVK for the resource + nsLister NamespaceListerExpansion // nLsister can list/get namespaces from the shared informer's store - nsLister v1CoreLister.NamespaceLister + // nsLister v1CoreLister.NamespaceLister // nsListerSynced returns true if the Namespace store has been synced at least once nsListerSynced cache.InformerSynced // pvLister can list/get policy violation from the shared informer's store @@ -77,7 +78,7 @@ func NewNamespaceController(kyvernoClient *kyvernoclient.Clientset, nsc.enqueueNs = nsc.enqueue nsc.syncHandler = nsc.syncNamespace - nsc.nsLister = nsInformer.Lister() + nsc.nsLister = NewNamespaceLister(nsInformer.Lister()) nsc.nsListerSynced = nsInformer.Informer().HasSynced nsc.pLister = pInformer.Lister() nsc.pvListerSynced = pInformer.Informer().HasSynced @@ -89,6 +90,7 @@ func NewNamespaceController(kyvernoClient *kyvernoclient.Clientset, return nsc } + func (nsc *NamespaceController) addNamespace(obj interface{}) { ns := obj.(*v1.Namespace) glog.V(4).Infof("Adding Namespace %s", ns.Name) @@ -167,7 +169,7 @@ func (nsc *NamespaceController) handleErr(err error, key interface{}) { } if nsc.queue.NumRequeues(key) < maxRetries { - glog.V(2).Infof("Error syncing Namespace %v: %v", key, err) + glog.V(2).Infof("Error syncing namespace %v: %v", key, err) nsc.queue.AddRateLimited(key) return } @@ -183,9 +185,9 @@ func (nsc *NamespaceController) syncNamespace(key string) error { defer func() { glog.V(4).Infof("Finished syncing namespace %q (%v)", key, time.Since(startTime)) }() - namespace, err := nsc.nsLister.Get(key) + namespace, err := nsc.nsLister.GetResource(key) if errors.IsNotFound(err) { - glog.V(2).Infof("Namespace %v has been deleted", key) + glog.V(2).Infof("namespace %v has been deleted", key) return nil } if err != nil { diff --git a/pkg/namespace/expansion.go b/pkg/namespace/expansion.go new file mode 100644 index 0000000000..1e256e556e --- /dev/null +++ b/pkg/namespace/expansion.go @@ -0,0 +1,46 @@ +package namespace + +import ( + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + v1CoreLister "k8s.io/client-go/listers/core/v1" +) + +//NamespaceListerExpansion ... +type NamespaceListerExpansion interface { + v1CoreLister.NamespaceLister + // List lists all Namespaces in the indexer. + ListResources(selector labels.Selector) (ret []*v1.Namespace, err error) + // GetsResource and injects gvk + GetResource(name string) (*v1.Namespace, error) +} + +//NamespaceLister ... +type NamespaceLister struct { + v1CoreLister.NamespaceLister +} + +//NewNamespaceLister returns a new NamespaceLister +func NewNamespaceLister(nsLister v1CoreLister.NamespaceLister) NamespaceListerExpansion { + nsl := NamespaceLister{ + nsLister, + } + return &nsl +} + +//ListResources is a wrapper to List and adds the resource kind information +// as the lister is specific to a gvk we can harcode the values here +func (nsl *NamespaceLister) ListResources(selector labels.Selector) (ret []*v1.Namespace, err error) { + namespaces, err := nsl.List(selector) + for index := range namespaces { + namespaces[index].SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Namespace")) + } + return namespaces, err +} + +//GetResource is a wrapper to get the resource and inject the GVK +func (nsl *NamespaceLister) GetResource(name string) (*v1.Namespace, error) { + namespace, err := nsl.Get(name) + namespace.SetGroupVersionKind(v1.SchemeGroupVersion.WithKind("Namespace")) + return namespace, err +} diff --git a/pkg/namespace/generation.go b/pkg/namespace/generation.go index 7323c11424..c21dc50638 100644 --- a/pkg/namespace/generation.go +++ b/pkg/namespace/generation.go @@ -87,53 +87,10 @@ func buildKey(policy, pv, kind, ns, name, rv string) string { return policy + "/" + pv + "/" + kind + "/" + ns + "/" + name + "/" + rv } -// func (nsc *NamespaceController) listPolicies(ns *corev1.Namespace) ([]*v1alpha1.Policy, error) { -// var fpolicies []*v1alpha1.Policy -// policies, err := c.policyLister.List(labels.NewSelector()) -// if err != nil { -// glog.Error("Unable to connect to policy controller. Unable to access policies not applying GENERATION rules") -// return nil, err -// } -// for _, p := range policies { -// // Check if the policy contains a generatoin rule -// for _, r := range p.Spec.Rules { -// if r.Generation != nil { -// // Check if the resource meets the description -// data, err := json.Marshal(ns) -// if err != nil { -// glog.Error(err) -// continue -// } -// // convert types of GVK -// nsGvk := schema.FromAPIVersionAndKind("v1", "Namespace") -// // Hardcode as we have a informer on specified gvk -// gvk := metav1.GroupVersionKind{Group: nsGvk.Group, Kind: nsGvk.Kind, Version: nsGvk.Version} -// if engine.ResourceMeetsDescription(data, r.MatchResources.ResourceDescription, r.ExcludeResources.ResourceDescription, gvk) { -// fpolicies = append(fpolicies, p) -// break -// } -// } -// } -// } - -// func (nsc *NamespaceController) processNamespace(ns *corev1.Namespace) error { -// //Get all policies and then verify if the namespace matches any of the defined selectors -// policies, err := c.listPolicies(ns) -// if err != nil { -// return err -// } -// // process policy on namespace -// for _, p := range policies { -// c.processPolicy(ns, p) -// } - -// return nil -// } - func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []info.PolicyInfo { var policyInfos []info.PolicyInfo // convert to unstructured - unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(namespace) + unstr, err := runtime.DefaultUnstructuredConverter.ToUnstructured(&namespace) if err != nil { glog.Infof("unable to convert to unstructured, not processing any policies: %v", err) return policyInfos @@ -142,9 +99,8 @@ func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []i ns := unstructured.Unstructured{Object: unstr} - // get all the policies that have a generate rule and apply on the namespace + // get all the policies that have a generate rule and resource description satifies the namespace // apply policy on resource - policies := listpolicies(ns, nsc.pLister) for _, policy := range policies { // pre-processing, check if the policy and resource version has been processed before @@ -162,7 +118,7 @@ func (nsc *NamespaceController) processNamespace(namespace corev1.Namespace) []i func listpolicies(ns unstructured.Unstructured, pLister lister.PolicyLister) []*kyverno.Policy { var filteredpolicies []*kyverno.Policy - glog.V(4).Infof("listing policies that namespace %s", ns.GetName()) + glog.V(4).Infof("listing policies for namespace %s", ns.GetName()) policies, err := pLister.List(labels.NewSelector()) if err != nil { glog.Errorf("failed to get list policies: %v", err) @@ -175,13 +131,13 @@ func listpolicies(ns unstructured.Unstructured, pLister lister.PolicyLister) []* } ok := engine.MatchesResourceDescription(ns, rule) if !ok { - glog.V(4).Infof("namespace %s does not satisfy the resource description for the rule ", ns.GetName()) + glog.V(4).Infof("namespace %s does not satisfy the resource description for the policy %s rule %s", ns.GetName(), policy.Name, rule.Name) continue } + glog.V(4).Infof("namespace %s satisfies resource description for policy %s rule %s", ns.GetName(), policy.Name, rule.Name) filteredpolicies = append(filteredpolicies, policy) } } - return filteredpolicies } @@ -197,125 +153,3 @@ func applyPolicy(client *client.Client, resource unstructured.Unstructured, poli return policyInfo } - -// // func (nsc *NamespaceController) listPolicies(ns *corev1.Namespace) ([]*v1alpha1.Policy, error) { -// // var fpolicies []*v1alpha1.Policy -// // policies, err := c.policyLister.List(labels.NewSelector()) -// // if err != nil { -// // glog.Error("Unable to connect to policy controller. Unable to access policies not applying GENERATION rules") -// // return nil, err -// // } -// // for _, p := range policies { -// // // Check if the policy contains a generatoin rule -// // for _, r := range p.Spec.Rules { -// // if r.Generation != nil { -// // // Check if the resource meets the description -// // data, err := json.Marshal(ns) -// // if err != nil { -// // glog.Error(err) -// // continue -// // } -// // // convert types of GVK -// // nsGvk := schema.FromAPIVersionAndKind("v1", "Namespace") -// // // Hardcode as we have a informer on specified gvk -// // gvk := metav1.GroupVersionKind{Group: nsGvk.Group, Kind: nsGvk.Kind, Version: nsGvk.Version} -// // if engine.ResourceMeetsDescription(data, r.MatchResources.ResourceDescription, r.ExcludeResources.ResourceDescription, gvk) { -// // fpolicies = append(fpolicies, p) -// // break -// // } -// // } -// // } -// // } - -// // return fpolicies, nil -// // } - -// func (nsc *NamespaceController) processPolicy(ns *corev1.Namespace, p *v1alpha1.Policy) { -// var eventInfo *event.Info -// var onViolation bool -// var msg string - -// policyInfo := info.NewPolicyInfo(p.Name, -// "Namespace", -// ns.Name, -// "", -// p.Spec.ValidationFailureAction) // Namespace has no namespace..WOW - -// // convert to unstructured -// unstrMap, err := runtime.DefaultUnstructuredConverter.ToUnstructured(ns) -// if err != nil { -// glog.Error(err) -// return -// } -// unstObj := unstructured.Unstructured{Object: unstrMap} -// ruleInfos := engine.Generate(c.client, p, unstObj) -// policyInfo.AddRuleInfos(ruleInfos) - -// // generate annotations on namespace -// c.createAnnotations(policyInfo) -// //TODO generate namespace on created resources - -// if !policyInfo.IsSuccessful() { -// glog.Infof("Failed to apply policy %s on resource %s %s", p.Name, ns.Kind, ns.Name) -// for _, r := range ruleInfos { -// glog.Warning(r.Msgs) - -// if msg = strings.Join(r.Msgs, " "); strings.Contains(msg, "rule configuration not present in resource") { -// onViolation = true -// msg = fmt.Sprintf(`Resource creation violates generate rule '%s' of policy '%s'`, r.Name, policyInfo.Name) -// } -// } - -// if onViolation { -// glog.Infof("Adding violation for generation rule of policy %s\n", policyInfo.Name) -// // Policy Violation -// v := violation.BuldNewViolation(policyInfo.Name, policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, event.PolicyViolation.String(), policyInfo.FailedRules()) -// c.violationBuilder.Add(v) -// } else { -// // Event -// eventInfo = event.NewEvent(policyKind, "", policyInfo.Name, event.RequestBlocked, -// event.FPolicyApplyBlockCreate, policyInfo.RName, policyInfo.GetRuleNames(false)) - -// glog.V(2).Infof("Request blocked event info has prepared for %s/%s\n", policyKind, policyInfo.Name) - -// c.eventController.Add(eventInfo) -// } -// return -// } - -// glog.Infof("Generation from policy %s has succesfully applied to %s/%s", p.Name, policyInfo.RKind, policyInfo.RName) - -// eventInfo = event.NewEvent(policyInfo.RKind, policyInfo.RNamespace, policyInfo.RName, -// event.PolicyApplied, event.SRulesApply, policyInfo.GetRuleNames(true), policyInfo.Name) - -// glog.V(2).Infof("Success event info has prepared for %s/%s\n", policyInfo.RKind, policyInfo.RName) - -// c.eventController.Add(eventInfo) -// } - -// func (nsc *NamespaceController) createAnnotations(pi *info.PolicyInfo) { -// //get resource -// obj, err := c.client.GetResource(pi.RKind, pi.RNamespace, pi.RName) -// if err != nil { -// glog.Error(err) -// return -// } -// // add annotation for policy application -// ann := obj.GetAnnotations() -// // Generation rules -// ann, gpatch, err := annotations.AddPolicyJSONPatch(ann, pi, info.Generation) -// if err != nil { -// glog.Error(err) -// return -// } -// if gpatch == nil { -// // nothing to patch -// return -// } -// // add the anotation to the resource -// _, err = c.client.PatchResource(pi.RKind, pi.RNamespace, pi.RName, gpatch) -// if err != nil { -// glog.Error(err) -// return -// } -// } diff --git a/pkg/policy/controller.go b/pkg/policy/controller.go index 2a3ce7af96..9f71dc1542 100644 --- a/pkg/policy/controller.go +++ b/pkg/policy/controller.go @@ -113,7 +113,8 @@ func NewPolicyController(kyvernoClient *kyvernoclient.Clientset, client *client. // resource manager // rebuild after 300 seconds/ 5 mins - pc.rm = NewResourceManager(300) + //TODO: pass the time in seconds instead of converting it internally + pc.rm = NewResourceManager(30) return &pc, nil } diff --git a/pkg/webhooks/mutation.go b/pkg/webhooks/mutation.go index 5e8333ce7e..64e7e94128 100644 --- a/pkg/webhooks/mutation.go +++ b/pkg/webhooks/mutation.go @@ -57,7 +57,6 @@ func (ws *WebhookServer) HandleMutation(request *v1beta1.AdmissionRequest) *v1be // continue // } //TODO: check if the GVK information is present in the request of we set it explicity here ? - glog.V(4).Infof("GVK is %v", resource.GroupVersionKind()) // resource.SetGroupVersionKind(schema.GroupVersionKind{Group: request.Kind.Group, Version: request.Kind.Version, Kind: request.Kind.Kind}) //TODO: passing policy value as we dont wont to modify the policy diff --git a/pkg/webhooks/registration.go b/pkg/webhooks/registration.go index adb35abbe5..e59d35dea5 100644 --- a/pkg/webhooks/registration.go +++ b/pkg/webhooks/registration.go @@ -285,11 +285,11 @@ func constructWebhook(name, servicePath string, caData []byte, validation bool) admregapi.Create, admregapi.Update, } - // Add operation DELETE for validation - if validation { - operationtypes = append(operationtypes, admregapi.Delete) + // // Add operation DELETE for validation + // if validation { + // operationtypes = append(operationtypes, admregapi.Delete) - } + // } return admregapi.Webhook{ Name: name, @@ -334,10 +334,10 @@ func constructDebugWebhook(name, url string, caData []byte, validation bool) adm admregapi.Create, admregapi.Update, } - // Add operation DELETE for validation - if validation { - operationtypes = append(operationtypes, admregapi.Delete) - } + // // Add operation DELETE for validation + // if validation { + // operationtypes = append(operationtypes, admregapi.Delete) + // } return admregapi.Webhook{ Name: name,