Files
capsule/internal/webhook/namespace/mutation/metadata.go
Oliver Bähler 6e8405d5f0 feat: refactor core webhooks (#1756)
* feat(webhook): add watchdog webhook to core

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* fix(controller): ensure managed metadata for namespaces on update

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore(controller): refactor core webhooks to generics

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: fix helm plugin installation

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

* chore: rename webhook to tenant-label

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>

---------

Signed-off-by: Oliver Bähler <oliverbaehler@hotmail.com>
2025-11-26 15:27:41 +01:00

146 lines
3.9 KiB
Go

// Copyright 2020-2025 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package mutation
import (
"context"
"encoding/json"
"net/http"
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"
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 metadataHandler struct {
cfg configuration.Configuration
}
func MetadataHandler(cfg configuration.Configuration) capsulewebhook.TypedHandler[*corev1.Namespace] {
return &metadataHandler{
cfg: cfg,
}
}
func (h *metadataHandler) OnCreate(client client.Client, ns *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
tnt, errResponse := utils.GetNamespaceTenant(ctx, client, ns, req, h.cfg, recorder)
if errResponse != nil {
return errResponse
}
if tnt == nil {
response := admission.Denied("Unable to assign namespace to tenant.")
return &response
}
labels, annotations, err := tenant.BuildNamespaceMetadataForTenant(ns, tnt)
if err != nil {
return utils.ErroredResponse(err)
}
managedMetadataOnly := tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.ManagedMetadataOnly
if managedMetadataOnly {
labels = mergeStringMap(ns.GetLabels(), labels)
annotations = mergeStringMap(ns.GetAnnotations(), annotations)
}
tenant.AddNamespaceNameLabels(labels, ns)
tenant.AddTenantNameLabel(labels, ns, tnt)
ns.SetLabels(labels)
ns.SetAnnotations(annotations)
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
}
}
func (h *metadataHandler) OnDelete(client.Client, *corev1.Namespace, admission.Decoder, record.EventRecorder) capsulewebhook.Func {
return func(context.Context, admission.Request) *admission.Response {
return nil
}
}
func (h *metadataHandler) OnUpdate(c client.Client, newNs *corev1.Namespace, oldNs *corev1.Namespace, decoder admission.Decoder, recorder record.EventRecorder) capsulewebhook.Func {
return func(ctx context.Context, req admission.Request) *admission.Response {
tnt, errResponse := utils.GetNamespaceTenant(ctx, c, oldNs, req, h.cfg, recorder)
if errResponse != nil {
return errResponse
}
if tnt == nil {
response := admission.Denied("Unable to assign namespace to tenant.")
return &response
}
o, err := json.Marshal(newNs.DeepCopy())
if err != nil {
response := admission.Errored(http.StatusInternalServerError, err)
return &response
}
labels, annotations, err := tenant.BuildNamespaceMetadataForTenant(newNs, tnt)
if err != nil {
return utils.ErroredResponse(err)
}
managedMetadataOnly := tnt.Spec.NamespaceOptions != nil && tnt.Spec.NamespaceOptions.ManagedMetadataOnly
if !managedMetadataOnly {
labels = mergeStringMap(newNs.GetLabels(), labels)
annotations = mergeStringMap(newNs.GetAnnotations(), annotations)
}
tenant.AddNamespaceNameLabels(labels, oldNs)
tenant.AddTenantNameLabel(labels, oldNs, tnt)
newNs.SetLabels(labels)
newNs.SetAnnotations(annotations)
obj, err := json.Marshal(newNs)
if err != nil {
response := admission.Errored(http.StatusInternalServerError, err)
return &response
}
response := admission.PatchResponseFromRaw(o, obj)
return &response
}
}
func mergeStringMap(dst, src map[string]string) map[string]string {
if len(src) == 0 {
return dst
}
if dst == nil {
dst = make(map[string]string, len(src))
}
for k, v := range src {
dst[k] = v
}
return dst
}