From 997d753546eb9a1ff4032fd339cd1650ee49766a Mon Sep 17 00:00:00 2001 From: Erkan Erol Date: Thu, 26 Aug 2021 18:25:09 +0300 Subject: [PATCH] Remove controller-runtime dependency from api package (#1483) Because of webhook codes, we have a dependency on contoller-runtime in our api package. Api packages should be as clean as possible. Also, we are hitting an issue* in test when we try to consume our api package because of this dependency. * https://github.com/kubernetes-sigs/controller-runtime/issues/878 Signed-off-by: Erkan Erol --- cmd/hyperconverged-cluster-webhook/main.go | 24 +-- .../hco/v1beta1/hyperconverged_webhook.go | 163 +--------------- pkg/components/components.go | 6 +- pkg/util/consts.go | 4 + pkg/webhooks/mutator/mutator.go | 120 ++++++++++++ pkg/webhooks/mutator/mutator_test.go | 175 +++++++++++++++++ pkg/webhooks/setup.go | 110 +++++++++++ .../setup_test.go} | 5 +- .../{webhooks.go => validator/validator.go} | 51 +---- .../validator_test.go} | 179 ++++-------------- 10 files changed, 479 insertions(+), 358 deletions(-) create mode 100644 pkg/webhooks/mutator/mutator.go create mode 100644 pkg/webhooks/mutator/mutator_test.go create mode 100644 pkg/webhooks/setup.go rename pkg/{apis/hco/v1beta1/hyperconverged_webhook_test.go => webhooks/setup_test.go} (77%) rename pkg/webhooks/{webhooks.go => validator/validator.go} (83%) rename pkg/webhooks/{webhooks_test.go => validator/validator_test.go} (87%) diff --git a/cmd/hyperconverged-cluster-webhook/main.go b/cmd/hyperconverged-cluster-webhook/main.go index 0d74969ce..a31e97afd 100644 --- a/cmd/hyperconverged-cluster-webhook/main.go +++ b/cmd/hyperconverged-cluster-webhook/main.go @@ -3,29 +3,32 @@ package main import ( "context" "fmt" - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - kubevirtv1 "kubevirt.io/client-go/api/v1" "os" - "github.com/kubevirt/hyperconverged-cluster-operator/cmd/cmdcommon" - "github.com/kubevirt/hyperconverged-cluster-operator/pkg/apis" + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/webhooks" + kubevirtv1 "kubevirt.io/client-go/api/v1" + "sigs.k8s.io/controller-runtime/pkg/client/config" "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/manager" "sigs.k8s.io/controller-runtime/pkg/manager/signals" - networkaddons "github.com/kubevirt/cluster-network-addons-operator/pkg/apis" - hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/pkg/apis/hco/v1beta1" - hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util" - vmimportv1beta1 "github.com/kubevirt/vm-import-operator/pkg/apis/v2v/v1beta1" + "github.com/kubevirt/hyperconverged-cluster-operator/cmd/cmdcommon" + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/apis" + openshiftconfigv1 "github.com/openshift/api/config/v1" corev1 "k8s.io/api/core/v1" apiruntime "k8s.io/apimachinery/pkg/runtime" _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" + logf "sigs.k8s.io/controller-runtime/pkg/log" + + networkaddons "github.com/kubevirt/cluster-network-addons-operator/pkg/apis" + hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util" + vmimportv1beta1 "github.com/kubevirt/vm-import-operator/pkg/apis/v2v/v1beta1" cdiv1beta1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1beta1" sspv1beta1 "kubevirt.io/ssp-operator/api/v1beta1" - logf "sigs.k8s.io/controller-runtime/pkg/log" ) // Change below variables to serve metrics on different host or port. @@ -97,8 +100,7 @@ func main() { // CreateServiceMonitors will automatically create the prometheus-operator ServiceMonitor resources // necessary to configure Prometheus to scrape metrics from this operator. - hwHandler := &webhooks.WebhookHandler{} - if err = (&hcov1beta1.HyperConverged{}).SetupWebhookWithManager(ctx, mgr, hwHandler, ci.IsOpenshift()); err != nil { + if err = webhooks.SetupWebhookWithManager(ctx, mgr, ci.IsOpenshift()); err != nil { logger.Error(err, "unable to create webhook", "webhook", "HyperConverged") eventEmitter.EmitEvent(nil, corev1.EventTypeWarning, "InitError", "Unable to create webhook") os.Exit(1) diff --git a/pkg/apis/hco/v1beta1/hyperconverged_webhook.go b/pkg/apis/hco/v1beta1/hyperconverged_webhook.go index db8ff60fc..eb010e9e7 100644 --- a/pkg/apis/hco/v1beta1/hyperconverged_webhook.go +++ b/pkg/apis/hco/v1beta1/hyperconverged_webhook.go @@ -1,98 +1,28 @@ package v1beta1 import ( - "context" "fmt" - "net/http" - "os" - "path/filepath" - "github.com/go-logr/logr" - admissionv1 "k8s.io/api/admission/v1" - admissionregistrationv1 "k8s.io/api/admissionregistration/v1" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" - "sigs.k8s.io/controller-runtime/pkg/webhook/admission" - - hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util" -) - -const ( - webHookCertDirEnv = "WEBHOOK_CERT_DIR" - DefaultWebhookCertDir = "/apiserver.local.config/certificates" - - WebhookCertName = "apiserver.crt" - WebhookKeyName = "apiserver.key" ) var ( - hcolog = logf.Log.WithName("hyperconverged-resource") -) - -func GetWebhookCertDir() string { - webhookCertDir := os.Getenv(webHookCertDirEnv) - if webhookCertDir != "" { - return webhookCertDir - } + whHandler ValidatorWebhookHandler - return DefaultWebhookCertDir -} + _ webhook.Validator = &HyperConverged{} +) -type WebhookHandlerIfs interface { - Init(logger logr.Logger, cli client.Client, namespace string, isOpenshift bool) +type ValidatorWebhookHandler interface { ValidateCreate(hc *HyperConverged) error ValidateUpdate(requested *HyperConverged, exists *HyperConverged) error ValidateDelete(hc *HyperConverged) error - HandleMutatingNsDelete(ns *corev1.Namespace, dryRun bool) (bool, error) } -var whHandler WebhookHandlerIfs - -func (r *HyperConverged) SetupWebhookWithManager(ctx context.Context, mgr ctrl.Manager, handler WebhookHandlerIfs, isOpenshift bool) error { - operatorNsEnv, nserr := hcoutil.GetOperatorNamespaceFromEnv() - if nserr != nil { - hcolog.Error(nserr, "failed to get operator namespace from the environment") - return nserr - } - - // Make sure the certificates are mounted, this should be handled by the OLM +func SetValidatorWebhookHandler(handler ValidatorWebhookHandler) { whHandler = handler - whHandler.Init(hcolog, mgr.GetClient(), operatorNsEnv, isOpenshift) - - webhookCertDir := GetWebhookCertDir() - certs := []string{filepath.Join(webhookCertDir, WebhookCertName), filepath.Join(webhookCertDir, WebhookKeyName)} - for _, fname := range certs { - if _, err := os.Stat(fname); err != nil { - hcolog.Error(err, "CSV certificates were not found, skipping webhook initialization") - return err - } - } - - if err := allowWatchAllNamespaces(ctx, mgr); err != nil { - return err - } - - bldr := ctrl.NewWebhookManagedBy(mgr).For(r) - - srv := mgr.GetWebhookServer() - srv.CertDir = GetWebhookCertDir() - srv.CertName = WebhookCertName - srv.KeyName = WebhookKeyName - srv.Port = hcoutil.WebhookPort - srv.Register(hcoutil.HCONSWebhookPath, &webhook.Admission{Handler: &nsMutator{}}) - - return bldr.Complete() } -var ( - _ webhook.Validator = &HyperConverged{} -) - func (r *HyperConverged) ValidateCreate() error { return whHandler.ValidateCreate(r) } @@ -109,86 +39,3 @@ func (r *HyperConverged) ValidateUpdate(old runtime.Object) error { func (r *HyperConverged) ValidateDelete() error { return whHandler.ValidateDelete(r) } - -// nsMutator mutates Ns requests -type nsMutator struct { - decoder *admission.Decoder -} - -func (a *nsMutator) Handle(_ context.Context, req admission.Request) admission.Response { - hcolog.Info("reaching nsMutator.Handle") - - if req.Operation == admissionv1.Delete { - return a.handleNsDelete(req) - } - - // ignoring other operations - return admission.Allowed("ignoring other operations") - -} - -func (a *nsMutator) handleNsDelete(req admission.Request) admission.Response { - ns := &corev1.Namespace{} - - // In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346 - // OldObject contains the object being deleted - err := a.decoder.DecodeRaw(req.OldObject, ns) - if err != nil { - hcolog.Error(err, "failed decoding namespace object") - return admission.Errored(http.StatusBadRequest, err) - } - - admitted, err := whHandler.HandleMutatingNsDelete(ns, *req.DryRun) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - - if admitted { - return admission.Allowed("the namespace doesn't contain HyperConverged CR, admitting its deletion") - } - return admission.Denied("HyperConverged CR is still present, please remove it before deleting the containing namespace") -} - -// The OLM limits the webhook scope to the namespaces that are defined in the OperatorGroup -// by setting namespaceSelector in the ValidatingWebhookConfiguration. We would like our webhook to intercept -// requests from all namespaces, and fail them if they're not in the correct namespace for HCO (for CREATE). -// Luckily the OLM does not watch and reconcile the ValidatingWebhookConfiguration so we can simply reset the -// namespaceSelector -func allowWatchAllNamespaces(ctx context.Context, mgr ctrl.Manager) error { - vwcList := &admissionregistrationv1.ValidatingWebhookConfigurationList{} - err := mgr.GetAPIReader().List(ctx, vwcList, client.MatchingLabels{"olm.webhook-description-generate-name": hcoutil.HcoValidatingWebhook}) - if err != nil { - hcolog.Error(err, "A validating webhook for the HCO was not found") - return err - } - - for _, vwc := range vwcList.Items { - update := false - - for i, wh := range vwc.Webhooks { - if wh.Name == hcoutil.HcoValidatingWebhook { - vwc.Webhooks[i].NamespaceSelector = &metav1.LabelSelector{MatchLabels: map[string]string{}} - update = true - } - } - - if update { - hcolog.Info("Removing namespace scope from webhook", "webhook", vwc.Name) - err = mgr.GetClient().Update(ctx, &vwc) - if err != nil { - hcolog.Error(err, "Failed updating webhook", "webhook", vwc.Name) - return err - } - } - } - return nil -} - -// nsMutator implements admission.DecoderInjector. -// A decoder will be automatically injected. - -// InjectDecoder injects the decoder. -func (a *nsMutator) InjectDecoder(d *admission.Decoder) error { - a.decoder = d - return nil -} diff --git a/pkg/components/components.go b/pkg/components/components.go index bb3735bda..66dc54b01 100644 --- a/pkg/components/components.go +++ b/pkg/components/components.go @@ -1158,11 +1158,11 @@ func InjectVolumesForWebHookCerts(deploy *appsv1.Deployment) { Items: []corev1.KeyToPath{ { Key: "tls.crt", - Path: hcov1beta1.WebhookCertName, + Path: hcoutil.WebhookCertName, }, { Key: "tls.key", - Path: hcov1beta1.WebhookKeyName, + Path: hcoutil.WebhookKeyName, }, }, }, @@ -1174,7 +1174,7 @@ func InjectVolumesForWebHookCerts(deploy *appsv1.Deployment) { deploy.Spec.Template.Spec.Containers[index].VolumeMounts = append(container.VolumeMounts, corev1.VolumeMount{ Name: "apiservice-cert", - MountPath: hcov1beta1.DefaultWebhookCertDir, + MountPath: hcoutil.DefaultWebhookCertDir, }) } } diff --git a/pkg/util/consts.go b/pkg/util/consts.go index 8ca13eff9..48dd4aa57 100644 --- a/pkg/util/consts.go +++ b/pkg/util/consts.go @@ -54,6 +54,10 @@ const ( DefaulterWebhookPath = "/mutate-hco-kubevirt-io-v1beta1-hyperconverged" WebhookPort = 4343 + WebhookCertName = "apiserver.crt" + WebhookKeyName = "apiserver.key" + DefaultWebhookCertDir = "/apiserver.local.config/certificates" + CliDownloadsServerPort = 8080 ) diff --git a/pkg/webhooks/mutator/mutator.go b/pkg/webhooks/mutator/mutator.go new file mode 100644 index 000000000..1f5ac1f5c --- /dev/null +++ b/pkg/webhooks/mutator/mutator.go @@ -0,0 +1,120 @@ +package mutator + +import ( + "context" + "net/http" + + admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/apis/hco/v1beta1" + hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util" +) + +const ( + ignoreOperationMessage = "ignoring other operations" + admittingDeletionMessage = "the namespace doesn't contain HyperConverged CR, admitting its deletion" + deniedDeletionMessage = "HyperConverged CR is still present, please remove it before deleting the containing namespace" +) + +var ( + logger = logf.Log.WithName("mutator") + + _ admission.Handler = &NsMutator{} +) + +// NsMutator mutates Ns requests +type NsMutator struct { + decoder *admission.Decoder + cli client.Client + namespace string +} + +func NewNsMutator(cli client.Client, namespace string) *NsMutator { + return &NsMutator{ + cli: cli, + namespace: namespace, + } +} + +func (nm *NsMutator) Handle(_ context.Context, req admission.Request) admission.Response { + logger.Info("reaching NsMutator.Handle") + + if req.Operation == admissionv1.Delete { + return nm.handleNsDelete(req) + } + + // ignoring other operations + return admission.Allowed(ignoreOperationMessage) + +} + +func (nm *NsMutator) handleNsDelete(req admission.Request) admission.Response { + ns := &corev1.Namespace{} + + // In reference to PR: https://github.com/kubernetes/kubernetes/pull/76346 + // OldObject contains the object being deleted + err := nm.decoder.DecodeRaw(req.OldObject, ns) + if err != nil { + logger.Error(err, "failed decoding namespace object") + return admission.Errored(http.StatusBadRequest, err) + } + + admitted, err := nm.handleMutatingNsDelete(ns) + if err != nil { + return admission.Errored(http.StatusInternalServerError, err) + } + + if admitted { + return admission.Allowed(admittingDeletionMessage) + } + + return admission.Denied(deniedDeletionMessage) +} + +// NsMutator implements admission.DecoderInjector. +// A decoder will be automatically injected. + +// InjectDecoder injects the decoder. +func (nm *NsMutator) InjectDecoder(d *admission.Decoder) error { + nm.decoder = d + return nil +} + +func (nm *NsMutator) handleMutatingNsDelete(ns *corev1.Namespace) (bool, error) { + logger.Info("validating namespace deletion", "name", ns.Name) + + if ns.Name != nm.namespace { + logger.Info("ignoring request for a different namespace") + return true, nil + } + + ctx := context.TODO() + hco := &v1beta1.HyperConverged{ + ObjectMeta: metav1.ObjectMeta{ + Name: hcoutil.HyperConvergedName, + Namespace: nm.namespace, + }, + } + + // Block the deletion if the namespace with a clear error message + // if HCO CR is still there + + found := &v1beta1.HyperConverged{} + err := nm.cli.Get(ctx, client.ObjectKeyFromObject(hco), found) + if err != nil { + if apierrors.IsNotFound(err) { + logger.Info("HCO CR doesn't not exist, allow namespace deletion") + return true, nil + } + logger.Error(err, "failed getting HyperConverged CR") + return false, err + } + logger.Info("HCO CR still exists, forbid namespace deletion") + return false, nil +} diff --git a/pkg/webhooks/mutator/mutator_test.go b/pkg/webhooks/mutator/mutator_test.go new file mode 100644 index 000000000..7444a428e --- /dev/null +++ b/pkg/webhooks/mutator/mutator_test.go @@ -0,0 +1,175 @@ +package mutator + +import ( + "context" + "errors" + "os" + "testing" + + admissionv1 "k8s.io/api/admission/v1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/serializer" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/controller/commonTestUtils" + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes/scheme" + + networkaddons "github.com/kubevirt/cluster-network-addons-operator/pkg/apis" + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/apis/hco/v1beta1" + vmimportv1beta1 "github.com/kubevirt/vm-import-operator/pkg/apis/v2v/v1beta1" + kubevirtv1 "kubevirt.io/client-go/api/v1" + cdiv1beta1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1beta1" + sspv1beta1 "kubevirt.io/ssp-operator/api/v1beta1" +) + +const ( + ResourceInvalidNamespace = "an-arbitrary-namespace" + HcoValidNamespace = "kubevirt-hyperconverged" +) + +var ( + ErrFakeHcoError = errors.New("fake HyperConverged error") +) + +func TestWebhook(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "Webhooks Suite") +} + +var _ = Describe("webhooks validator", func() { + s := scheme.Scheme + + for _, f := range []func(*runtime.Scheme) error{ + v1beta1.AddToScheme, + cdiv1beta1.AddToScheme, + kubevirtv1.AddToScheme, + networkaddons.AddToScheme, + sspv1beta1.AddToScheme, + vmimportv1beta1.AddToScheme, + corev1.AddToScheme, + } { + Expect(f(s)).To(BeNil()) + } + + codecFactory := serializer.NewCodecFactory(s) + corev1Codec := codecFactory.LegacyCodec(corev1.SchemeGroupVersion) + + Context("Check mutating webhook for namespace deletion", func() { + BeforeEach(func() { + Expect(os.Setenv("OPERATOR_NAMESPACE", HcoValidNamespace)).To(BeNil()) + }) + + cr := &v1beta1.HyperConverged{ + ObjectMeta: metav1.ObjectMeta{ + Name: util.HyperConvergedName, + Namespace: HcoValidNamespace, + }, + Spec: v1beta1.HyperConvergedSpec{}, + } + + var ns runtime.Object = &corev1.Namespace{ + TypeMeta: metav1.TypeMeta{}, + ObjectMeta: metav1.ObjectMeta{ + Name: HcoValidNamespace, + }, + } + + It("should allow the delete of the namespace if Hyperconverged CR doesn't exist", func() { + cli := commonTestUtils.InitClient(nil) + nsMutator := initMutator(s, cli) + req := admission.Request{AdmissionRequest: newRequest(admissionv1.Delete, ns, corev1Codec)} + + res := nsMutator.Handle(context.TODO(), req) + Expect(res.Allowed).To(BeTrue()) + }) + + It("should not allow the delete of the namespace if Hyperconverged CR exists", func() { + cli := commonTestUtils.InitClient([]runtime.Object{cr}) + nsMutator := initMutator(s, cli) + req := admission.Request{AdmissionRequest: newRequest(admissionv1.Delete, ns, corev1Codec)} + + res := nsMutator.Handle(context.TODO(), req) + Expect(res.Allowed).To(BeFalse()) + }) + + It("should not allow when the request is not valid", func() { + cli := commonTestUtils.InitClient([]runtime.Object{cr}) + nsMutator := initMutator(s, cli) + req := admission.Request{AdmissionRequest: admissionv1.AdmissionRequest{Operation: admissionv1.Delete}} + + res := nsMutator.Handle(context.TODO(), req) + Expect(res.Allowed).To(BeFalse()) + }) + + It("should not allow the delete of the namespace if failed to get Hyperconverged CR", func() { + cli := commonTestUtils.InitClient([]runtime.Object{cr}) + + cli.InitiateGetErrors(func(key client.ObjectKey) error { + if key.Name == util.HyperConvergedName { + return ErrFakeHcoError + } + return nil + }) + + nsMutator := initMutator(s, cli) + req := admission.Request{AdmissionRequest: newRequest(admissionv1.Delete, ns, corev1Codec)} + + res := nsMutator.Handle(context.TODO(), req) + Expect(res.Allowed).To(BeFalse()) + }) + + It("should ignore other namespaces even if Hyperconverged CR exists", func() { + cli := commonTestUtils.InitClient([]runtime.Object{cr}) + otherNs := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ResourceInvalidNamespace, + }, + } + + nsMutator := initMutator(s, cli) + req := admission.Request{AdmissionRequest: newRequest(admissionv1.Delete, otherNs, corev1Codec)} + + res := nsMutator.Handle(context.TODO(), req) + Expect(res.Allowed).To(BeTrue()) + }) + + It("should allow other operations", func() { + cli := commonTestUtils.InitClient([]runtime.Object{cr}) + nsMutator := initMutator(s, cli) + req := admission.Request{AdmissionRequest: newRequest(admissionv1.Update, ns, corev1Codec)} + + res := nsMutator.Handle(context.TODO(), req) + Expect(res.Allowed).To(BeTrue()) + }) + }) + +}) + +func initMutator(s *runtime.Scheme, testClient client.Client) *NsMutator { + nsMutator := NewNsMutator(testClient, HcoValidNamespace) + + decoder, err := admission.NewDecoder(s) + ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) + + err = nsMutator.InjectDecoder(decoder) + ExpectWithOffset(1, err).ShouldNot(HaveOccurred()) + + return nsMutator +} + +func newRequest(operation admissionv1.Operation, object runtime.Object, encoder runtime.Encoder) admissionv1.AdmissionRequest { + return admissionv1.AdmissionRequest{ + Operation: operation, + OldObject: runtime.RawExtension{ + Raw: []byte(runtime.EncodeOrDie(encoder, object)), + Object: object, + }, + } +} diff --git a/pkg/webhooks/setup.go b/pkg/webhooks/setup.go new file mode 100644 index 000000000..8328aaf0c --- /dev/null +++ b/pkg/webhooks/setup.go @@ -0,0 +1,110 @@ +package webhooks + +import ( + "context" + "os" + "path/filepath" + + hcov1beta1 "github.com/kubevirt/hyperconverged-cluster-operator/pkg/apis/hco/v1beta1" + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/webhooks/mutator" + "github.com/kubevirt/hyperconverged-cluster-operator/pkg/webhooks/validator" + + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + + hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util" +) + +const ( + webHookCertDirEnv = "WEBHOOK_CERT_DIR" +) + +var ( + logger = logf.Log.WithName("webhook-setup") +) + +func GetWebhookCertDir() string { + webhookCertDir := os.Getenv(webHookCertDirEnv) + if webhookCertDir != "" { + return webhookCertDir + } + + return hcoutil.DefaultWebhookCertDir +} + +func SetupWebhookWithManager(ctx context.Context, mgr ctrl.Manager, isOpenshift bool) error { + operatorNsEnv, nserr := hcoutil.GetOperatorNamespaceFromEnv() + if nserr != nil { + logger.Error(nserr, "failed to get operator namespace from the environment") + return nserr + } + + whHandler := validator.NewWebhookHandler(logger, mgr.GetClient(), operatorNsEnv, isOpenshift) + hcov1beta1.SetValidatorWebhookHandler(whHandler) + + nsMutator := mutator.NewNsMutator(mgr.GetClient(), operatorNsEnv) + + // Make sure the certificates are mounted, this should be handled by the OLM + webhookCertDir := GetWebhookCertDir() + certs := []string{filepath.Join(webhookCertDir, hcoutil.WebhookCertName), filepath.Join(webhookCertDir, hcoutil.WebhookKeyName)} + for _, fname := range certs { + if _, err := os.Stat(fname); err != nil { + logger.Error(err, "CSV certificates were not found, skipping webhook initialization") + return err + } + } + + if err := allowWatchAllNamespaces(ctx, mgr); err != nil { + return err + } + + bldr := ctrl.NewWebhookManagedBy(mgr).For(&hcov1beta1.HyperConverged{}) + + srv := mgr.GetWebhookServer() + srv.CertDir = GetWebhookCertDir() + srv.CertName = hcoutil.WebhookCertName + srv.KeyName = hcoutil.WebhookKeyName + srv.Port = hcoutil.WebhookPort + srv.Register(hcoutil.HCONSWebhookPath, &webhook.Admission{Handler: nsMutator}) + + return bldr.Complete() +} + +// The OLM limits the webhook scope to the namespaces that are defined in the OperatorGroup +// by setting namespaceSelector in the ValidatingWebhookConfiguration. We would like our webhook to intercept +// requests from all namespaces, and fail them if they're not in the correct namespace for HCO (for CREATE). +// Luckily the OLM does not watch and reconcile the ValidatingWebhookConfiguration so we can simply reset the +// namespaceSelector +func allowWatchAllNamespaces(ctx context.Context, mgr ctrl.Manager) error { + vwcList := &admissionregistrationv1.ValidatingWebhookConfigurationList{} + err := mgr.GetAPIReader().List(ctx, vwcList, client.MatchingLabels{"olm.webhook-description-generate-name": hcoutil.HcoValidatingWebhook}) + if err != nil { + logger.Error(err, "A validating webhook for the HCO was not found") + return err + } + + for _, vwc := range vwcList.Items { + update := false + + for i, wh := range vwc.Webhooks { + if wh.Name == hcoutil.HcoValidatingWebhook { + vwc.Webhooks[i].NamespaceSelector = &metav1.LabelSelector{MatchLabels: map[string]string{}} + update = true + } + } + + if update { + logger.Info("Removing namespace scope from webhook", "webhook", vwc.Name) + err = mgr.GetClient().Update(ctx, &vwc) + if err != nil { + logger.Error(err, "Failed updating webhook", "webhook", vwc.Name) + return err + } + } + } + return nil +} diff --git a/pkg/apis/hco/v1beta1/hyperconverged_webhook_test.go b/pkg/webhooks/setup_test.go similarity index 77% rename from pkg/apis/hco/v1beta1/hyperconverged_webhook_test.go rename to pkg/webhooks/setup_test.go index b58c645ac..239180c30 100644 --- a/pkg/apis/hco/v1beta1/hyperconverged_webhook_test.go +++ b/pkg/webhooks/setup_test.go @@ -1,6 +1,7 @@ -package v1beta1 +package webhooks import ( + hcoutil "github.com/kubevirt/hyperconverged-cluster-operator/pkg/util" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "os" @@ -9,7 +10,7 @@ import ( var _ = Describe("Hyperconverged API: Webhook", func() { Context("Test GetWebhookCertDir", func() { It("should return default value, if the env var is not set", func() { - Expect(GetWebhookCertDir()).Should(Equal(DefaultWebhookCertDir)) + Expect(GetWebhookCertDir()).Should(Equal(hcoutil.DefaultWebhookCertDir)) }) It("should return the value of the env var, if set", func() { diff --git a/pkg/webhooks/webhooks.go b/pkg/webhooks/validator/validator.go similarity index 83% rename from pkg/webhooks/webhooks.go rename to pkg/webhooks/validator/validator.go index 8f4b99351..173639ea1 100644 --- a/pkg/webhooks/webhooks.go +++ b/pkg/webhooks/validator/validator.go @@ -1,4 +1,4 @@ -package webhooks +package validator import ( "context" @@ -11,8 +11,6 @@ import ( "github.com/go-logr/logr" networkaddonsv1 "github.com/kubevirt/cluster-network-addons-operator/pkg/apis/networkaddonsoperator/v1" vmimportv1beta1 "github.com/kubevirt/vm-import-operator/pkg/apis/v2v/v1beta1" - corev1 "k8s.io/api/core/v1" - apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" kubevirtv1 "kubevirt.io/client-go/api/v1" cdiv1beta1 "kubevirt.io/containerized-data-importer/pkg/apis/core/v1beta1" @@ -28,6 +26,8 @@ const ( updateDryRunTimeOut = time.Second * 3 ) +var _ v1beta1.ValidatorWebhookHandler = &WebhookHandler{} + type WebhookHandler struct { logger logr.Logger cli client.Client @@ -35,11 +35,13 @@ type WebhookHandler struct { isOpenshift bool } -func (wh *WebhookHandler) Init(logger logr.Logger, cli client.Client, namespace string, isOpenshift bool) { - wh.logger = logger - wh.cli = cli - wh.namespace = namespace - wh.isOpenshift = isOpenshift +func NewWebhookHandler(logger logr.Logger, cli client.Client, namespace string, isOpenshift bool) *WebhookHandler { + return &WebhookHandler{ + logger: logger, + cli: cli, + namespace: namespace, + isOpenshift: isOpenshift, + } } func (wh WebhookHandler) ValidateCreate(hc *v1beta1.HyperConverged) error { @@ -220,39 +222,6 @@ func (wh WebhookHandler) ValidateDelete(hc *v1beta1.HyperConverged) error { return nil } -func (wh WebhookHandler) HandleMutatingNsDelete(ns *corev1.Namespace, dryRun bool) (bool, error) { - wh.logger.Info("validating namespace deletion", "name", ns.Name) - - if ns.Name != wh.namespace { - wh.logger.Info("ignoring request for a different namespace") - return true, nil - } - - ctx := context.TODO() - hco := &v1beta1.HyperConverged{ - ObjectMeta: metav1.ObjectMeta{ - Name: hcoutil.HyperConvergedName, - Namespace: wh.namespace, - }, - } - - // Block the deletion if the namespace with a clear error message - // if HCO CR is still there - - found := &v1beta1.HyperConverged{} - err := wh.cli.Get(ctx, client.ObjectKeyFromObject(hco), found) - if err != nil { - if apierrors.IsNotFound(err) { - wh.logger.Info("HCO CR doesn't not exist, allow namespace deletion") - return true, nil - } - wh.logger.Error(err, "failed getting HyperConverged CR") - return false, err - } - wh.logger.Info("HCO CR still exists, forbid namespace deletion") - return false, nil -} - func (wh WebhookHandler) validateCertConfig(hc *v1beta1.HyperConverged) error { minimalDuration := metav1.Duration{Duration: 10 * time.Minute} diff --git a/pkg/webhooks/webhooks_test.go b/pkg/webhooks/validator/validator_test.go similarity index 87% rename from pkg/webhooks/webhooks_test.go rename to pkg/webhooks/validator/validator_test.go index bc66aa5bb..d02a74740 100644 --- a/pkg/webhooks/webhooks_test.go +++ b/pkg/webhooks/validator/validator_test.go @@ -1,4 +1,4 @@ -package webhooks +package validator import ( "context" @@ -97,7 +97,7 @@ const ( invalidCnaAnnotation = `[{"op": "wrongOp", "path": "/spec/kubeMacPool", "value": {"rangeStart": "1.1.1.1.1.1", "rangeEnd": "5.5.5.5.5.5" }}]` ) -var _ = Describe("webhooks handler", func() { +var _ = Describe("webhooks validator", func() { s := scheme.Scheme for _, f := range []func(*runtime.Scheme) error{ v1beta1.AddToScheme, @@ -118,8 +118,7 @@ var _ = Describe("webhooks handler", func() { }) cli := fake.NewClientBuilder().WithScheme(s).Build() - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) It("should accept creation of a resource with a valid namespace", func() { err := wh.ValidateCreate(cr) @@ -234,8 +233,7 @@ var _ = Describe("webhooks handler", func() { kv := operands.NewKubeVirtWithNameOnly(hco) Expect(cli.Delete(ctx, kv)).ToNot(HaveOccurred()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -252,8 +250,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(getUpdateError(kvUpdateFailure)) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -271,8 +268,8 @@ var _ = Describe("webhooks handler", func() { cdi, err := operands.NewCDI(hco) Expect(err).ToNot(HaveOccurred()) Expect(cli.Delete(ctx, cdi)).To(BeNil()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -288,8 +285,7 @@ var _ = Describe("webhooks handler", func() { It("should return error if dry-run update of CDI CR returns error", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(getUpdateError(cdiUpdateFailure)) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -305,8 +301,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(getUpdateError(noFailure)) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -323,8 +318,7 @@ var _ = Describe("webhooks handler", func() { cna, err := operands.NewNetworkAddons(hco) Expect(err).ToNot(HaveOccurred()) Expect(cli.Delete(ctx, cna)).To(BeNil()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -341,8 +335,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(getUpdateError(networkUpdateFailure)) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -358,8 +351,7 @@ var _ = Describe("webhooks handler", func() { ctx := context.TODO() cli := getFakeClient(hco) Expect(cli.Delete(ctx, operands.NewSSP(hco))).To(BeNil()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -375,8 +367,7 @@ var _ = Describe("webhooks handler", func() { It("should return error if dry-run update of SSP CR returns error", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(getUpdateError(sspUpdateFailure)) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -393,8 +384,7 @@ var _ = Describe("webhooks handler", func() { ctx := context.TODO() cli := getFakeClient(hco) Expect(cli.Delete(ctx, operands.NewVMImportForCR(hco))).To(BeNil()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -411,8 +401,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(getUpdateError(vmImportUpdateFailure)) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -428,8 +417,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(initiateTimeout) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -445,8 +433,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(initiateTimeout) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -458,8 +445,7 @@ var _ = Describe("webhooks handler", func() { Context("test permitted host devices update validation", func() { It("should allow unique PCI Host Device", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -484,8 +470,7 @@ var _ = Describe("webhooks handler", func() { It("should allow unique Mediate Host Device", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -517,8 +502,7 @@ var _ = Describe("webhooks handler", func() { kv, err := operands.NewKubeVirt(hco) Expect(err).ToNot(HaveOccurred()) Expect(cli.Delete(ctx, kv)).To(BeNil()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, false) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, false) newHco := commonTestUtils.NewHco() newHco.Spec.Infra = v1beta1.HyperConvergedConfig{ @@ -545,8 +529,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(getUpdateError(vmImportUpdateFailure)) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -576,8 +559,7 @@ var _ = Describe("webhooks handler", func() { kv := operands.NewKubeVirtWithNameOnly(hco) Expect(cli.Delete(context.TODO(), kv)).ToNot(HaveOccurred()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -589,8 +571,7 @@ var _ = Describe("webhooks handler", func() { It("should allow updating of live migration", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -606,8 +587,7 @@ var _ = Describe("webhooks handler", func() { It("should fail if live migration is wrong", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -639,8 +619,7 @@ var _ = Describe("webhooks handler", func() { kv := operands.NewKubeVirtWithNameOnly(hco) Expect(cli.Delete(context.TODO(), kv)).ToNot(HaveOccurred()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -652,8 +631,7 @@ var _ = Describe("webhooks handler", func() { It("should allow updating of cert config", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -672,8 +650,7 @@ var _ = Describe("webhooks handler", func() { func(newHco v1beta1.HyperConverged, errorMsg string) { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) err := wh.ValidateUpdate(&newHco, hco) Expect(err).To(HaveOccurred()) @@ -840,8 +817,7 @@ var _ = Describe("webhooks handler", func() { It("should validate deletion", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) err := wh.ValidateDelete(hco) Expect(err).ToNot(HaveOccurred()) @@ -860,8 +836,7 @@ var _ = Describe("webhooks handler", func() { It("should reject if KV deletion fails", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) cli.InitiateDeleteErrors(func(obj client.Object) error { if unstructed, ok := obj.(runtime.Unstructured); ok { @@ -881,8 +856,7 @@ var _ = Describe("webhooks handler", func() { It("should reject if CDI deletion fails", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) cli.InitiateDeleteErrors(func(obj client.Object) error { if unstructed, ok := obj.(runtime.Unstructured); ok { @@ -906,8 +880,7 @@ var _ = Describe("webhooks handler", func() { kv := operands.NewKubeVirtWithNameOnly(hco) Expect(cli.Delete(ctx, kv)).To(BeNil()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) err := wh.ValidateDelete(hco) Expect(err).ToNot(HaveOccurred()) @@ -916,8 +889,7 @@ var _ = Describe("webhooks handler", func() { It("should reject if getting KV failed for not-not-exists error", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) cli.InitiateGetErrors(func(key client.ObjectKey) error { if key.Name == "kubevirt-kubevirt-hyperconverged" { @@ -938,8 +910,7 @@ var _ = Describe("webhooks handler", func() { cdi := operands.NewCDIWithNameOnly(hco) Expect(cli.Delete(ctx, cdi)).To(BeNil()) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) err := wh.ValidateDelete(hco) Expect(err).ToNot(HaveOccurred()) @@ -948,8 +919,7 @@ var _ = Describe("webhooks handler", func() { It("should reject if getting CDI failed for not-not-exists error", func() { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) cli.InitiateGetErrors(func(key client.ObjectKey) error { if key.Name == "cdi-kubevirt-hyperconverged" { @@ -974,8 +944,7 @@ var _ = Describe("webhooks handler", func() { DescribeTable("should accept if annotation is valid", func(annotationName, annotation string) { cli := getFakeClient(hco) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -994,8 +963,7 @@ var _ = Describe("webhooks handler", func() { cli := getFakeClient(hco) cli.InitiateUpdateErrors(initiateTimeout) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) + wh := NewWebhookHandler(logger, cli, HcoValidNamespace, true) newHco := &v1beta1.HyperConverged{} hco.DeepCopyInto(newHco) @@ -1013,80 +981,6 @@ var _ = Describe("webhooks handler", func() { ) }) - Context("Check mutating webhook for namespace deletion", func() { - BeforeEach(func() { - Expect(os.Setenv("OPERATOR_NAMESPACE", HcoValidNamespace)).To(BeNil()) - }) - - cr := &v1beta1.HyperConverged{ - ObjectMeta: metav1.ObjectMeta{ - Name: util.HyperConvergedName, - Namespace: HcoValidNamespace, - }, - Spec: v1beta1.HyperConvergedSpec{}, - } - - ns := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: HcoValidNamespace, - }, - } - - It("should allow the delete of the namespace if Hyperconverged CR doesn't exist", func() { - cli := commonTestUtils.InitClient(nil) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) - - allowed, err := wh.HandleMutatingNsDelete(ns, false) - Expect(err).ToNot(HaveOccurred()) - Expect(allowed).To(BeTrue()) - }) - - It("should not allow the delete of the namespace if Hyperconverged CR exists", func() { - cli := commonTestUtils.InitClient([]runtime.Object{cr}) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) - - allowed, err := wh.HandleMutatingNsDelete(ns, false) - Expect(err).ToNot(HaveOccurred()) - Expect(allowed).To(BeFalse()) - }) - - It("should not allow the delete of the namespace if failed to get Hyperconverged CR", func() { - cli := commonTestUtils.InitClient([]runtime.Object{cr}) - - cli.InitiateGetErrors(func(key client.ObjectKey) error { - if key.Name == util.HyperConvergedName { - return ErrFakeHcoError - } - return nil - }) - - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) - - allowed, err := wh.HandleMutatingNsDelete(ns, false) - Expect(err).To(HaveOccurred()) - Expect(err).Should(Equal(ErrFakeHcoError)) - Expect(allowed).To(BeFalse()) - }) - - It("should ignore other namespaces even if Hyperconverged CR exists", func() { - cli := commonTestUtils.InitClient([]runtime.Object{cr}) - wh := &WebhookHandler{} - wh.Init(logger, cli, HcoValidNamespace, true) - - otherNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: ResourceInvalidNamespace, - }, - } - - allowed, err := wh.HandleMutatingNsDelete(otherNs, false) - Expect(err).ToNot(HaveOccurred()) - Expect(allowed).To(BeTrue()) - }) - }) }) func newHyperConvergedConfig() *sdkapi.NodePlacement { @@ -1146,7 +1040,6 @@ const ( ) var ( - ErrFakeHcoError = errors.New("fake HyperConverged error") ErrFakeKvError = errors.New("fake KubeVirt error") ErrFakeCdiError = errors.New("fake CDI error") ErrFakeNetworkError = errors.New("fake Network error")