diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index e035b987..d20f6984 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -9,6 +9,7 @@ on: - '.github/workflows/e2e.yml' - 'api/**' - 'controllers/**' + - 'internal/**' - 'pkg/**' - 'e2e/*' - 'Dockerfile' diff --git a/cmd/main.go b/cmd/main.go index baab0669..1aa4e152 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -47,7 +47,6 @@ import ( "github.com/projectcapsule/capsule/internal/webhook/gateway" "github.com/projectcapsule/capsule/internal/webhook/ingress" "github.com/projectcapsule/capsule/internal/webhook/misc" - "github.com/projectcapsule/capsule/internal/webhook/namespace" namespacemutation "github.com/projectcapsule/capsule/internal/webhook/namespace/mutation" namespacevalidation "github.com/projectcapsule/capsule/internal/webhook/namespace/validation" "github.com/projectcapsule/capsule/internal/webhook/networkpolicy" @@ -286,7 +285,7 @@ func main() { tenantvalidation.WarningHandler(), ), route.NamespaceValidation( - namespace.NamespaceHandler( + namespacevalidation.NamespaceHandler( cfg, namespacevalidation.PatchHandler(cfg), namespacevalidation.FreezeHandler(cfg), @@ -296,7 +295,7 @@ func main() { ), ), route.NamespaceMutation( - namespace.NamespaceHandler( + namespacemutation.NamespaceHandler( cfg, namespacemutation.OwnerReferenceHandler(cfg), namespacemutation.MetadataHandler(cfg), diff --git a/internal/webhook/namespace/handler.go b/internal/webhook/namespace/mutation/handler.go similarity index 89% rename from internal/webhook/namespace/handler.go rename to internal/webhook/namespace/mutation/handler.go index 3b7f581e..e0039ee8 100644 --- a/internal/webhook/namespace/handler.go +++ b/internal/webhook/namespace/mutation/handler.go @@ -1,7 +1,7 @@ // Copyright 2020-2025 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 -package namespace +package mutation import ( "context" @@ -19,19 +19,19 @@ import ( ) func NamespaceHandler(configuration configuration.Configuration, handlers ...webhook.TypedHandler[*corev1.Namespace]) webhook.Handler { - return &adminHandler{ + return &handler{ cfg: configuration, handlers: handlers, } } -type adminHandler struct { +type handler struct { cfg configuration.Configuration handlers []webhook.TypedHandler[*corev1.Namespace] } //nolint:dupl -func (h *adminHandler) OnCreate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { +func (h *handler) OnCreate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) @@ -64,7 +64,7 @@ func (h *adminHandler) OnCreate(c client.Client, decoder admission.Decoder, reco } //nolint:dupl -func (h *adminHandler) OnDelete(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { +func (h *handler) OnDelete(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) @@ -96,7 +96,7 @@ func (h *adminHandler) OnDelete(c client.Client, decoder admission.Decoder, reco } } -func (h *adminHandler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { +func (h *handler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) diff --git a/internal/webhook/namespace/mutation/ownerreference.go b/internal/webhook/namespace/mutation/ownerreference.go index 0dbcb542..64ca1362 100644 --- a/internal/webhook/namespace/mutation/ownerreference.go +++ b/internal/webhook/namespace/mutation/ownerreference.go @@ -52,7 +52,18 @@ func (h *ownerReferenceHandler) OnCreate(c client.Client, ns *corev1.Namespace, tenant.AddTenantNameLabel(labels, ns, tnt) ns.SetLabels(labels) - response := patchResponseForOwnerRef(c, tnt.DeepCopy(), ns, recorder) + if err := assignToTenant(c, tnt, ns, recorder); err != nil { + return utils.ErroredResponse(err) + } + + marshaled, err := json.Marshal(ns) + if err != nil { + response := admission.Errored(http.StatusInternalServerError, err) + + return &response + } + + response := admission.PatchResponseFromRaw(req.Object.Raw, marshaled) return &response } @@ -163,26 +174,3 @@ func assignToTenant( return nil } - -func patchResponseForOwnerRef( - c client.Client, - tnt *capsulev1beta2.Tenant, - ns *corev1.Namespace, - recorder record.EventRecorder, -) admission.Response { - o, err := json.Marshal(ns.DeepCopy()) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - - if err := assignToTenant(c, tnt, ns, recorder); err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - - obj, err := json.Marshal(ns) - if err != nil { - return admission.Errored(http.StatusInternalServerError, err) - } - - return admission.PatchResponseFromRaw(o, obj) -} diff --git a/internal/webhook/namespace/validation/freezed.go b/internal/webhook/namespace/validation/freezed.go index fe3498c7..57b63599 100644 --- a/internal/webhook/namespace/validation/freezed.go +++ b/internal/webhook/namespace/validation/freezed.go @@ -11,10 +11,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" - "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - "github.com/projectcapsule/capsule/pkg/utils/tenant" "github.com/projectcapsule/capsule/pkg/utils/users" ) @@ -22,26 +21,18 @@ type freezedHandler struct { cfg configuration.Configuration } -func FreezeHandler(configuration configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { +func FreezeHandler(configuration configuration.Configuration) capsulewebhook.TypedHandlerWithTenant[*corev1.Namespace] { return &freezedHandler{cfg: configuration} } -func (r *freezedHandler) OnCreate( +func (h *freezedHandler) OnCreate( c client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, ) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil { - return nil - } - if tnt.Spec.Cordoned { recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be attached, the current Tenant is freezed", ns.GetName()) @@ -54,23 +45,15 @@ func (r *freezedHandler) OnCreate( } } -func (r *freezedHandler) OnDelete( +func (h *freezedHandler) OnDelete( c client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, ) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil { - return nil - } - - if tnt.Spec.Cordoned && users.IsCapsuleUser(ctx, c, r.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + if tnt.Spec.Cordoned && users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be deleted, the current Tenant is freezed", req.Name) response := admission.Denied("the selected Tenant is freezed") @@ -82,24 +65,16 @@ func (r *freezedHandler) OnDelete( } } -func (r *freezedHandler) OnUpdate( +func (h *freezedHandler) OnUpdate( c client.Client, ns *corev1.Namespace, old *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, ) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil { - return nil - } - - if tnt.Spec.Cordoned && users.IsCapsuleUser(ctx, c, r.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + if tnt.Spec.Cordoned && users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { recorder.Eventf(tnt, corev1.EventTypeWarning, "TenantFreezed", "Namespace %s cannot be updated, the current Tenant is freezed", ns.GetName()) response := admission.Denied("the selected Tenant is freezed") diff --git a/internal/webhook/namespace/validation/handler.go b/internal/webhook/namespace/validation/handler.go new file mode 100644 index 00000000..41de7fb8 --- /dev/null +++ b/internal/webhook/namespace/validation/handler.go @@ -0,0 +1,174 @@ +// Copyright 2020-2025 Project Capsule Authors +// SPDX-License-Identifier: Apache-2.0 + +package validation + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" + "github.com/projectcapsule/capsule/internal/webhook" + "github.com/projectcapsule/capsule/internal/webhook/utils" + "github.com/projectcapsule/capsule/pkg/api/meta" + "github.com/projectcapsule/capsule/pkg/configuration" + "github.com/projectcapsule/capsule/pkg/utils/tenant" + "github.com/projectcapsule/capsule/pkg/utils/users" +) + +func NamespaceHandler(configuration configuration.Configuration, handlers ...webhook.TypedHandlerWithTenant[*corev1.Namespace]) webhook.Handler { + return &handler{ + cfg: configuration, + handlers: handlers, + } +} + +type handler struct { + cfg configuration.Configuration + handlers []webhook.TypedHandlerWithTenant[*corev1.Namespace] +} + +//nolint:dupl +func (h *handler) OnCreate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) + + if !userIsAdmin && !users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + return nil + } + + ns := &corev1.Namespace{} + if err := decoder.Decode(req, ns); err != nil { + return utils.ErroredResponse(err) + } + + tnt, err := h.verifyReference(ctx, c, ns) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + for _, hndl := range h.handlers { + if response := hndl.OnCreate(c, ns, decoder, recorder, tnt)(ctx, req); response != nil { + return response + } + } + + return nil + } +} + +//nolint:dupl +func (h *handler) OnDelete(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) + + if !userIsAdmin && !users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + return nil + } + + ns := &corev1.Namespace{} + if err := decoder.Decode(req, ns); err != nil { + return utils.ErroredResponse(err) + } + + tnt, err := h.verifyReference(ctx, c, ns) + if err != nil { + return utils.ErroredResponse(err) + } + + if tnt == nil { + return nil + } + + for _, hndl := range h.handlers { + if response := hndl.OnDelete(c, ns, decoder, recorder, tnt)(ctx, req); response != nil { + return response + } + } + + return nil + } +} + +func (h *handler) OnUpdate(c client.Client, decoder admission.Decoder, recorder record.EventRecorder) webhook.Func { + return func(ctx context.Context, req admission.Request) *admission.Response { + userIsAdmin := users.IsAdminUser(req, h.cfg.Administrators()) + + if !userIsAdmin && !users.IsCapsuleUser(ctx, c, h.cfg, req.UserInfo.Username, req.UserInfo.Groups) { + return nil + } + + ns := &corev1.Namespace{} + if err := decoder.Decode(req, ns); err != nil { + return utils.ErroredResponse(err) + } + + oldNs := &corev1.Namespace{} + if err := decoder.DecodeRaw(req.OldObject, oldNs); err != nil { + return utils.ErroredResponse(err) + } + + oldTenant, err := h.verifyReference(ctx, c, oldNs) + if err != nil { + return utils.ErroredResponse(err) + } + + if oldTenant == nil { + return nil + } + + newTenant, err := h.verifyReference(ctx, c, ns) + if err != nil { + return utils.ErroredResponse(err) + } + + if newTenant.GetName() != oldTenant.GetName() { + err := fmt.Errorf("namespace can not be migrated between tenants") + + return utils.ErroredResponse(err) + } + + for _, hndl := range h.handlers { + if response := hndl.OnUpdate(c, ns, oldNs, decoder, recorder, oldTenant)(ctx, req); response != nil { + return response + } + } + + return nil + } +} + +func (h *handler) verifyReference( + ctx context.Context, + c client.Client, + ns *corev1.Namespace, +) (*capsulev1beta2.Tenant, error) { + tenantByOwnerreference, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) + if err != nil { + return nil, err + } + + name := "" + if tenantByOwnerreference != nil { + name = tenantByOwnerreference.GetName() + } + + if name != ns.Labels[meta.TenantLabel] { + return nil, fmt.Errorf( + "namespace label %q does not match owner reference %q", + ns.Labels[meta.TenantLabel], + name, + ) + } + + return tenantByOwnerreference, nil +} diff --git a/internal/webhook/namespace/validation/patch.go b/internal/webhook/namespace/validation/patch.go index 18dfe741..4bfeb192 100644 --- a/internal/webhook/namespace/validation/patch.go +++ b/internal/webhook/namespace/validation/patch.go @@ -9,14 +9,12 @@ import ( "net/http" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" - "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/configuration" "github.com/projectcapsule/capsule/pkg/utils/users" ) @@ -25,51 +23,54 @@ type patchHandler struct { cfg configuration.Configuration } -func PatchHandler(configuration configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { +func PatchHandler(configuration configuration.Configuration) capsulewebhook.TypedHandlerWithTenant[*corev1.Namespace] { return &patchHandler{cfg: configuration} } -func (r *patchHandler) OnCreate(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *patchHandler) OnCreate( + client.Client, + *corev1.Namespace, + admission.Decoder, + record.EventRecorder, + *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (r *patchHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *patchHandler) OnDelete( + client.Client, + *corev1.Namespace, + admission.Decoder, + record.EventRecorder, + *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (r *patchHandler) OnUpdate( +func (h *patchHandler) OnUpdate( c client.Client, ns *corev1.Namespace, old *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, ) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { e := fmt.Sprintf("namespace/%s can not be patched", ns.Name) - if label, ok := ns.Labels[meta.TenantLabel]; ok { - // retrieving the selected Tenant - tnt := &capsulev1beta2.Tenant{} - if err := c.Get(ctx, types.NamespacedName{Name: label}, tnt); err != nil { - response := admission.Errored(http.StatusBadRequest, err) + ok, err := users.IsTenantOwner(ctx, c, h.cfg, tnt, req.UserInfo) + if err != nil { + response := admission.Errored(http.StatusBadRequest, err) - return &response - } + return &response + } - ok, err := users.IsTenantOwner(ctx, c, r.cfg, tnt, req.UserInfo) - if err != nil { - response := admission.Errored(http.StatusBadRequest, err) - - return &response - } - - if ok { - return nil - } + if ok { + return nil } recorder.Eventf(ns, corev1.EventTypeWarning, "NamespacePatch", e) diff --git a/internal/webhook/namespace/validation/prefix.go b/internal/webhook/namespace/validation/prefix.go index daed58ca..064e3797 100644 --- a/internal/webhook/namespace/validation/prefix.go +++ b/internal/webhook/namespace/validation/prefix.go @@ -13,39 +13,30 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" - "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/configuration" - "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type prefixHandler struct { cfg configuration.Configuration } -func PrefixHandler(configuration configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] { +func PrefixHandler(configuration configuration.Configuration) capsulewebhook.TypedHandlerWithTenant[*corev1.Namespace] { return &prefixHandler{ cfg: configuration, } } -func (r *prefixHandler) OnCreate( - clt client.Client, +func (h *prefixHandler) OnCreate( + c client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, ) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tnt, err := tenant.GetTenantByOwnerreferences(ctx, clt, ns.OwnerReferences) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil { - return nil - } - - if exp, _ := r.cfg.ProtectedNamespaceRegexp(); exp != nil { + if exp, _ := h.cfg.ProtectedNamespaceRegexp(); exp != nil { if matched := exp.MatchString(ns.GetName()); matched { response := admission.Denied(fmt.Sprintf("Creating namespaces with name matching %s regexp is not allowed; please, reach out to the system administrators", exp.String())) @@ -53,7 +44,7 @@ func (r *prefixHandler) OnCreate( } } - if r.cfg.ForceTenantPrefix() { + if h.cfg.ForceTenantPrefix() { if tnt.Spec.ForceTenantPrefix != nil && !*tnt.Spec.ForceTenantPrefix { return nil } @@ -71,13 +62,26 @@ func (r *prefixHandler) OnCreate( } } -func (r *prefixHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *prefixHandler) OnUpdate( + client.Client, + *corev1.Namespace, + *corev1.Namespace, + admission.Decoder, + record.EventRecorder, + *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (r *prefixHandler) OnUpdate(client.Client, *corev1.Namespace, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *prefixHandler) OnDelete( + client.Client, + *corev1.Namespace, + admission.Decoder, + record.EventRecorder, + *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } diff --git a/internal/webhook/namespace/validation/quota.go b/internal/webhook/namespace/validation/quota.go index fbbd8924..855b35a2 100644 --- a/internal/webhook/namespace/validation/quota.go +++ b/internal/webhook/namespace/validation/quota.go @@ -12,45 +12,60 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" - "github.com/projectcapsule/capsule/internal/webhook/utils" - "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type quotaHandler struct{} -func QuotaHandler() capsulewebhook.TypedHandler[*corev1.Namespace] { +func QuotaHandler() capsulewebhook.TypedHandlerWithTenant[*corev1.Namespace] { return "aHandler{} } -func (r *quotaHandler) OnCreate(client client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (h *quotaHandler) OnCreate( + c client.Client, + ns *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - return r.handle(client, ns, recorder, ctx, req) + return h.handle(ctx, c, recorder, ns, tnt) } } -func (r *quotaHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *quotaHandler) OnDelete( + client.Client, + *corev1.Namespace, + admission.Decoder, + record.EventRecorder, + *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil } } -func (r *quotaHandler) OnUpdate(client client.Client, ns *corev1.Namespace, _ *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (h *quotaHandler) OnUpdate( + c client.Client, + ns *corev1.Namespace, + _ *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - return r.handle(client, ns, recorder, ctx, req) + return h.handle(ctx, c, recorder, ns, tnt) } } -func (r *quotaHandler) handle(c client.Client, ns *corev1.Namespace, recorder record.EventRecorder, ctx context.Context, req admission.Request) *admission.Response { - tnt, err := tenant.GetTenantByOwnerreferences(ctx, c, ns.OwnerReferences) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil { - return nil - } - +func (h *quotaHandler) handle( + ctx context.Context, + c client.Client, + recorder record.EventRecorder, + ns *corev1.Namespace, + tnt *capsulev1beta2.Tenant, +) *admission.Response { if tnt.IsFull() { // Checking if the Namespace already exists. // If this is the case, no need to return the quota exceeded error: diff --git a/internal/webhook/namespace/validation/user_metadata.go b/internal/webhook/namespace/validation/user_metadata.go index 331d44ea..c0be31db 100644 --- a/internal/webhook/namespace/validation/user_metadata.go +++ b/internal/webhook/namespace/validation/user_metadata.go @@ -12,34 +12,25 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" capsulewebhook "github.com/projectcapsule/capsule/internal/webhook" - "github.com/projectcapsule/capsule/internal/webhook/utils" "github.com/projectcapsule/capsule/pkg/api" - "github.com/projectcapsule/capsule/pkg/utils/tenant" ) type userMetadataHandler struct{} -func UserMetadataHandler() capsulewebhook.TypedHandler[*corev1.Namespace] { +func UserMetadataHandler() capsulewebhook.TypedHandlerWithTenant[*corev1.Namespace] { return &userMetadataHandler{} } -func (r *userMetadataHandler) OnCreate(client client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func { +func (h *userMetadataHandler) OnCreate( + c client.Client, + ns *corev1.Namespace, + decoder admission.Decoder, + recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - ns := &corev1.Namespace{} - if err := decoder.Decode(req, ns); err != nil { - return utils.ErroredResponse(err) - } - - tnt, err := tenant.GetTenantByOwnerreferences(ctx, client, ns.OwnerReferences) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil { - return nil - } - if tnt.Spec.NamespaceOptions != nil { err := api.ValidateForbidden(ns.Annotations, tnt.Spec.NamespaceOptions.ForbiddenAnnotations) if err != nil { @@ -64,23 +55,15 @@ func (r *userMetadataHandler) OnCreate(client client.Client, ns *corev1.Namespac } } -func (r *userMetadataHandler) OnUpdate( +func (h *userMetadataHandler) OnUpdate( client client.Client, newNs *corev1.Namespace, oldNs *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder, + tnt *capsulev1beta2.Tenant, ) capsulewebhook.Func { return func(ctx context.Context, req admission.Request) *admission.Response { - tnt, err := tenant.GetTenantByOwnerreferences(ctx, client, newNs.OwnerReferences) - if err != nil { - return utils.ErroredResponse(err) - } - - if tnt == nil { - return nil - } - if len(tnt.Spec.NodeSelector) > 0 { v, ok := newNs.GetAnnotations()["scheduler.alpha.kubernetes.io/node-selector"] if !ok { @@ -164,7 +147,13 @@ func (r *userMetadataHandler) OnUpdate( } } -func (r *userMetadataHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func { +func (h *userMetadataHandler) OnDelete( + client.Client, + *corev1.Namespace, + admission.Decoder, + record.EventRecorder, + *capsulev1beta2.Tenant, +) capsulewebhook.Func { return func(context.Context, admission.Request) *admission.Response { return nil }