Files
capsule/internal/webhook/pvc/handler.go
T
Oliver BählerandGitHub 93fafdc2fd fix: do not mutate on update and bound pvcs (#2073)
* fix: do not mutate on update and bound pvcs

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

* fix: do not mutate on update and bound pvcs

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>

---------

Signed-off-by: Oliver Baehler <oliver@sudo-i.net>
2026-08-11 12:03:16 +02:00

77 lines
2.2 KiB
Go

// Copyright 2020-2026 Project Capsule Authors
// SPDX-License-Identifier: Apache-2.0
package pvc
import (
admissionv1 "k8s.io/api/admission/v1"
corev1 "k8s.io/api/core/v1"
apiequality "k8s.io/apimachinery/pkg/api/equality"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
"github.com/projectcapsule/capsule/pkg/runtime/handlers"
)
func Handler(handler ...handlers.TypedHandlerWithTenant[*corev1.PersistentVolumeClaim]) handlers.Handler {
return &handlers.TypedTenantHandler[*corev1.PersistentVolumeClaim]{
Factory: func() *corev1.PersistentVolumeClaim {
return &corev1.PersistentVolumeClaim{}
},
Handlers: handler,
Predicate: requiresPVCSpecValidation,
}
}
func MutatingHandler(handler ...handlers.TypedHandlerWithTenant[*corev1.PersistentVolumeClaim]) handlers.Handler {
return &handlers.TypedTenantHandler[*corev1.PersistentVolumeClaim]{
Factory: func() *corev1.PersistentVolumeClaim {
return &corev1.PersistentVolumeClaim{}
},
Handlers: handler,
Predicate: func(
req admission.Request,
pvc *corev1.PersistentVolumeClaim,
oldPVC *corev1.PersistentVolumeClaim,
) bool {
if !requiresPVCSpecValidation(req, pvc, oldPVC) {
return false
}
if pvc.Spec.Selector != nil {
return true
}
return req.Operation == admissionv1.Create && pvc.Spec.VolumeName != ""
},
}
}
func requiresPVCSpecValidation(
req admission.Request,
pvc *corev1.PersistentVolumeClaim,
oldPVC *corev1.PersistentVolumeClaim,
) bool {
// A bound PVC's volume binding fields, including its selector, are immutable.
// Reapplying the tenant selector during an update would make otherwise valid
// metadata or resize updates fail for claims created before Capsule enforced it.
if req.Operation == admissionv1.Update &&
isBoundPVC(oldPVC) {
return false
}
// Finalizer cleanup must remain possible after a bound PV has disappeared.
// Continue validating any update that changes the PVC spec.
if req.Operation != admissionv1.Update ||
pvc == nil ||
oldPVC == nil ||
pvc.DeletionTimestamp == nil {
return true
}
return !apiequality.Semantic.DeepEqual(pvc.Spec, oldPVC.Spec)
}
func isBoundPVC(pvc *corev1.PersistentVolumeClaim) bool {
return pvc != nil && pvc.Status.Phase == corev1.ClaimBound
}