chore: allow args in deny and allow helper functions (#1963)

Signed-off-by: bakito <github@bakito.ch>
This commit is contained in:
Marc Brugger
2026-06-10 19:09:06 +02:00
committed by GitHub
parent 34262c5536
commit f5d2d7d67b
21 changed files with 126 additions and 183 deletions
+44 -58
View File
@@ -103,14 +103,12 @@ func (h *objectCalculationHandler) OnCreate(
evaluated, err := h.evaluateMatchedQuotas(ctx, u, matched)
if err != nil {
finalResp = ad.Deny(
fmt.Sprintf(
"creating resource %s/%s (%s) cannot be admitted because custom quota usage could not be calculated: %v",
req.Namespace,
req.Name,
req.Kind.String(),
err,
),
finalResp = ad.Denyf(
"creating resource %s/%s (%s) cannot be admitted because custom quota usage could not be calculated: %v",
req.Namespace,
req.Name,
req.Kind.String(),
err,
)
return nil
@@ -166,17 +164,15 @@ func (h *objectCalculationHandler) OnCreate(
"inflightReserved", reserved.String(),
)
finalResp = ad.Deny(
fmt.Sprintf(
"creating resource exceeds limit for %s %q (requested=%s, currentUsed=%s, available=%s, limit=%s, inflightReserved=%s)",
quotaTypeName(item.IsGlobal),
item.Name,
item.Usage.String(),
effectiveUsed.String(),
available.String(),
item.Limit.String(),
reserved.String(),
),
finalResp = ad.Denyf(
"creating resource exceeds limit for %s %q (requested=%s, currentUsed=%s, available=%s, limit=%s, inflightReserved=%s)",
quotaTypeName(item.IsGlobal),
item.Name,
item.Usage.String(),
effectiveUsed.String(),
available.String(),
item.Limit.String(),
reserved.String(),
)
return nil
@@ -194,12 +190,10 @@ func (h *objectCalculationHandler) OnCreate(
})
if err != nil {
if apierrors.IsConflict(err) {
return ad.Deny(
fmt.Sprintf(
"custom quota admission could not reserve usage due to concurrent quota updates after %d attempts; please retry the request: %v",
customAdmissionBackoff.Steps,
err,
),
return ad.Denyf(
"custom quota admission could not reserve usage due to concurrent quota updates after %d attempts; please retry the request: %v",
customAdmissionBackoff.Steps,
err,
)
}
@@ -247,14 +241,12 @@ func (h *objectCalculationHandler) OnUpdate(
oldEvaluated, err := h.evaluateMatchedQuotas(ctx, oldObj, oldMatched)
if err != nil {
finalResp = ad.Deny(
fmt.Sprintf(
"updating resource %s/%s (%s) cannot be admitted because previous custom quota usage could not be calculated: %v",
req.Namespace,
req.Name,
req.Kind.String(),
err,
),
finalResp = ad.Denyf(
"updating resource %s/%s (%s) cannot be admitted because previous custom quota usage could not be calculated: %v",
req.Namespace,
req.Name,
req.Kind.String(),
err,
)
return nil
@@ -262,14 +254,12 @@ func (h *objectCalculationHandler) OnUpdate(
newEvaluated, err := h.evaluateMatchedQuotas(ctx, newObj, newMatched)
if err != nil {
finalResp = ad.Deny(
fmt.Sprintf(
"updating resource %s/%s (%s) cannot be admitted because new custom quota usage could not be calculated: %v",
req.Namespace,
req.Name,
req.Kind.String(),
err,
),
finalResp = ad.Denyf(
"updating resource %s/%s (%s) cannot be admitted because new custom quota usage could not be calculated: %v",
req.Namespace,
req.Name,
req.Kind.String(),
err,
)
return nil
@@ -397,17 +387,15 @@ func (h *objectCalculationHandler) OnUpdate(
available = resource.MustParse("0")
}
finalResp = ad.Deny(
fmt.Sprintf(
"updating resource exceeds limit for %s %q (requested=%s, currentUsed=%s, available=%s, limit=%s, inflightReserved=%s)",
quotaTypeName(base.IsGlobal),
base.Name,
newUsage.String(),
effectiveUsed.String(),
available.String(),
base.Limit.String(),
reserved.String(),
),
finalResp = ad.Denyf(
"updating resource exceeds limit for %s %q (requested=%s, currentUsed=%s, available=%s, limit=%s, inflightReserved=%s)",
quotaTypeName(base.IsGlobal),
base.Name,
newUsage.String(),
effectiveUsed.String(),
available.String(),
base.Limit.String(),
reserved.String(),
)
return nil
@@ -432,12 +420,10 @@ func (h *objectCalculationHandler) OnUpdate(
})
if err != nil {
if apierrors.IsConflict(err) {
return ad.Deny(
fmt.Sprintf(
"custom quota admission could not reserve usage due to concurrent quota updates after %d attempts; please retry the request: %v",
customAdmissionBackoff.Steps,
err,
),
return ad.Denyf(
"custom quota admission could not reserve usage due to concurrent quota updates after %d attempts; please retry the request: %v",
customAdmissionBackoff.Steps,
err,
)
}
@@ -49,9 +49,7 @@ func (h *customQuotaValidationHandler) OnCreate(
}
if err := quota.ValidateQuantity(q.Spec.Limit); err != nil {
response := admission.Denied(fmt.Sprintf("invalid spec.limit: %v", err))
return &response
return ad.Denyf("invalid spec.limit: %v", err)
}
return nil
@@ -103,7 +101,7 @@ func (h *customQuotaValidationHandler) OnUpdate(
}
if err := quota.ValidateQuantity(newQuota.Spec.Limit); err != nil {
return ad.Deny(fmt.Sprintf("invalid spec.limit: %v", err))
return ad.Denyf("invalid spec.limit: %v", err)
}
used := oldQuota.Status.Usage.Used
@@ -113,18 +111,16 @@ func (h *customQuotaValidationHandler) OnUpdate(
if hasUsage {
if sourcesChanged(oldQuota.Spec.Sources, newQuota.Spec.Sources) {
return ad.Deny(
fmt.Sprintf("spec.sources cannot be changed while usage is recorded (usage: %s); create a new CustomQuota instead", used.String()),
return ad.Denyf(
"spec.sources cannot be changed while usage is recorded (usage: %s); create a new CustomQuota instead", used.String(),
)
}
if newQuota.Spec.Limit.Cmp(used) < 0 {
return ad.Deny(
fmt.Sprintf(
"spec.limit cannot be lowered below current usage (%s); requested limit: %s",
used.String(),
newQuota.Spec.Limit.String(),
),
return ad.Denyf(
"spec.limit cannot be lowered below current usage (%s); requested limit: %s",
used.String(),
newQuota.Spec.Limit.String(),
)
}
}
@@ -49,9 +49,7 @@ func (h *globalCustomQuotaValidationHandler) OnCreate(
}
if err := quota.ValidateQuantity(q.Spec.Limit); err != nil {
response := admission.Denied(fmt.Sprintf("invalid spec.limit: %v", err))
return &response
return ad.Denyf("invalid spec.limit: %v", err)
}
return nil
@@ -102,9 +100,7 @@ func (h *globalCustomQuotaValidationHandler) OnUpdate(
}
if err := quota.ValidateQuantity(newQuota.Spec.Limit); err != nil {
return ad.Deny(
fmt.Sprintf("invalid spec.limit: %v", err),
)
return ad.Denyf("invalid spec.limit: %v", err)
}
used := oldQuota.Status.Usage.Used
@@ -114,18 +110,16 @@ func (h *globalCustomQuotaValidationHandler) OnUpdate(
if hasUsage {
if sourcesChanged(oldQuota.Spec.Sources, newQuota.Spec.Sources) {
return ad.Deny(
fmt.Sprintf("spec.sources cannot be changed while usage is recorded (usage: %s); create a new CustomQuota instead", used.String()),
return ad.Denyf(
"spec.sources cannot be changed while usage is recorded (usage: %s); create a new CustomQuota instead", used.String(),
)
}
if newQuota.Spec.Limit.Cmp(used) < 0 {
return ad.Deny(
fmt.Sprintf(
"spec.limit cannot be lowered below current usage (%s); requested limit: %s",
used.String(),
newQuota.Spec.Limit.String(),
),
return ad.Denyf(
"spec.limit cannot be lowered below current usage (%s); requested limit: %s",
used.String(),
newQuota.Spec.Limit.String(),
)
}
}
+1 -2
View File
@@ -5,7 +5,6 @@ package generic
import (
"context"
"fmt"
"k8s.io/client-go/tools/events"
"sigs.k8s.io/controller-runtime/pkg/client"
@@ -56,5 +55,5 @@ func (h *cordoningHandler) OnUpdate(
}
func (h *cordoningHandler) cordonHandler(req admission.Request) *admission.Response {
return ad.Deny(fmt.Sprintf("The current namespace '%s' is cordoned. The attempted operation %s for %s/%s/%s/%s is not permitted during cordoning status.", req.Namespace, req.Operation, req.RequestKind.Group, req.RequestKind.Version, req.RequestKind.Kind, req.Name))
return ad.Denyf("The current namespace '%s' is cordoned. The attempted operation %s for %s/%s/%s/%s is not permitted during cordoning status.", req.Namespace, req.Operation, req.RequestKind.Group, req.RequestKind.Version, req.RequestKind.Kind, req.Name)
}
+9 -14
View File
@@ -5,7 +5,6 @@ package generic
import (
"context"
"fmt"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apiserver/pkg/authentication/serviceaccount"
@@ -105,12 +104,10 @@ func (h *replicaHandler) handler(
}
}
return ad.Deny(
fmt.Sprintf(
"resource %s is managed by a global capsule replication %s",
req.Name,
global.Items[0].GetName(),
),
return ad.Denyf(
"resource %s is managed by a global capsule replication %s",
req.Name,
global.Items[0].GetName(),
)
}
@@ -132,13 +129,11 @@ func (h *replicaHandler) handler(
}
}
return ad.Deny(
fmt.Sprintf(
"resource %s is managed by a tenant capsule replication %s/%s",
req.Name,
local.Items[0].GetName(),
local.Items[0].GetNamespace(),
),
return ad.Denyf(
"resource %s is managed by a tenant capsule replication %s/%s",
req.Name,
local.Items[0].GetName(),
local.Items[0].GetNamespace(),
)
}
@@ -106,9 +106,7 @@ func (r *hostnames) validate(
if errors.As(err, &hostnameNotValidErr) {
recorder.Eventf(ingress.GetClientObject(), tnt, corev1.EventTypeWarning, evt.ReasonIngressHostnameNotValid, evt.ActionValidationDenied, "Ingress %s/%s hostname is not valid", ingress.Namespace(), ingress.Name())
response := admission.Denied(err.Error())
return &response
return ad.Deny(err.Error())
}
return ad.ErroredResponse(err)
@@ -5,7 +5,6 @@ package ingress
import (
"context"
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
@@ -92,7 +91,7 @@ func (h *wildcard) validate(
// In case of wildcard, generate an event and then return.
recorder.Eventf(ingress.GetClientObject(), &tnt, corev1.EventTypeWarning, evt.ReasonWildcardDenied, evt.ActionValidationDenied, "%s %s/%s cannot be %s", req.Kind.String(), req.Namespace, req.Name, strings.ToLower(string(req.Operation)))
return ad.Deny(fmt.Sprintf("Wildcard denied for tenant %s\n", tnt.GetName()))
return ad.Denyf("Wildcard denied for tenant %s", tnt.GetName())
}
}
}
@@ -55,13 +55,11 @@ func (h *ownerReferenceHandler) OnCreate(
}
if tnt == nil {
response := admission.Denied(
return ad.Deny(
"Unable to assign namespace to tenant. Please use " +
meta.TenantLabel +
" label when creating a namespace",
)
return &response
}
labels := ns.GetLabels()
@@ -5,7 +5,6 @@ package validation
import (
"context"
"fmt"
"strings"
corev1 "k8s.io/api/core/v1"
@@ -43,11 +42,9 @@ func (h *prefixHandler) OnCreate(
return func(ctx context.Context, req admission.Request) *admission.Response {
if exp, _ := h.cfg.ProtectedNamespaceRegexp(); exp != nil {
if exp.MatchString(ns.GetName()) {
return ad.Deny(
fmt.Sprintf(
"Creating namespaces with name matching %s regexp is not allowed; please, reach out to the system administrators",
exp.String(),
),
return ad.Denyf(
"Creating namespaces with name matching %s regexp is not allowed; please, reach out to the system administrators",
exp.String(),
)
}
}
@@ -73,11 +70,9 @@ func (h *prefixHandler) OnCreate(
ns.GetName(),
)
return ad.Deny(
fmt.Sprintf(
"The namespace doesn't match the tenant prefix, expected prefix %q",
expectedPrefix,
),
return ad.Denyf(
"The namespace doesn't match the tenant prefix, expected prefix %q",
expectedPrefix,
)
}
@@ -5,7 +5,6 @@ package validation
import (
"context"
"fmt"
"regexp"
corev1 "k8s.io/api/core/v1"
@@ -118,16 +117,16 @@ func validateRequiredMapCreate(kind string, required map[string]string, actual m
for key, exp := range required {
val, ok := actual[key]
if !ok {
return ad.Deny(fmt.Sprintf("required %s %q not present", kind, key))
return ad.Denyf("required %s %q not present", kind, key)
}
re, reErr := regexp.Compile(exp)
if reErr != nil {
return ad.Deny(fmt.Sprintf("invalid required %s regex for %q: %q: %v", kind, key, exp, reErr))
return ad.Denyf("invalid required %s regex for %q: %q: %v", kind, key, exp, reErr)
}
if !re.MatchString(val) {
return ad.Deny(fmt.Sprintf("required %s %q value %q does not match regex %q", kind, key, val, exp))
return ad.Denyf("required %s %q value %q does not match regex %q", kind, key, val, exp)
}
}
@@ -149,16 +148,16 @@ func validateRequiredMapUpdate(kind string, required map[string]string, newMap,
}
if !newOK {
return ad.Deny(fmt.Sprintf("required %s %q not present", kind, key))
return ad.Denyf("required %s %q not present", kind, key)
}
re, reErr := regexp.Compile(exp)
if reErr != nil {
return ad.Deny(fmt.Sprintf("invalid required %s regex for %q: %q: %v", kind, key, exp, reErr))
return ad.Denyf("invalid required %s regex for %q: %q: %v", kind, key, exp, reErr)
}
if !re.MatchString(valNew) {
return ad.Deny(fmt.Sprintf("required %s %q value %q does not match regex %q", mismatchKind, key, valNew, exp))
return ad.Denyf("required %s %q value %q does not match regex %q", mismatchKind, key, valNew, exp)
}
}
@@ -51,7 +51,7 @@ func (h *claimValidationHandler) OnDelete(
}
if claim.IsBoundInResourcePool() {
return ad.Deny(fmt.Sprintf("cannot delete the pool while claim is used in resourcepool %s", claim.Status.Pool.Name))
return ad.Denyf("cannot delete the pool while claim is used in resourcepool %s", claim.Status.Pool.Name)
}
return nil
@@ -78,7 +78,7 @@ func (h *claimValidationHandler) OnUpdate(
if oldClaim.IsBoundInResourcePool() {
if oldClaim.Spec.Pool != newClaim.Spec.Pool || !reflect.DeepEqual(oldClaim.Spec.ResourceClaims, newClaim.Spec.ResourceClaims) {
return ad.Deny(fmt.Sprintf("cannot change the requested resources while claim is allocated to a resourcepool %s", oldClaim.Status.Pool.Name))
return ad.Denyf("cannot change the requested resources while claim is allocated to a resourcepool %s", oldClaim.Status.Pool.Name)
}
}
@@ -5,7 +5,6 @@ package resourcepool
import (
"context"
"fmt"
"github.com/go-logr/logr"
"k8s.io/apimachinery/pkg/api/equality"
@@ -79,22 +78,18 @@ func (h *poolValidationHandler) OnUpdate(
continue
}
return ad.Deny(
fmt.Sprintf(
"can not remove resource %s as it is still being allocated. Remove corresponding claims or keep the resources in the pool",
resourceName,
),
return ad.Denyf(
"can not remove resource %s as it is still being allocated. Remove corresponding claims or keep the resources in the pool",
resourceName,
)
}
if allocation.Cmp(qt) < 0 {
return ad.Deny(
fmt.Sprintf(
"can not reduce %s usage to %s because quantity %s is claimed . Remove corresponding claims or keep the resources in the pool",
resourceName,
allocation.String(),
qt.String(),
),
return ad.Denyf(
"can not reduce %s usage to %s because quantity %s is claimed . Remove corresponding claims or keep the resources in the pool",
resourceName,
allocation.String(),
qt.String(),
)
}
}
@@ -107,7 +107,5 @@ func (h *ownerPromotion) handle(
msg,
)
response := admission.Denied(msg)
return &response
return ad.Deny(msg)
}
@@ -103,7 +103,5 @@ func (h *promotion) handle(
msg,
)
response := admission.Denied(msg)
return &response
return ad.Deny(msg)
}
@@ -5,7 +5,6 @@ package validation
import (
"context"
"fmt"
"regexp"
"k8s.io/client-go/tools/events"
@@ -80,7 +79,7 @@ func (h *forbiddenAnnotationsRegexHandler) validate(tnt *capsulev1beta2.Tenant,
for scope, annotation := range annotationsToCheck {
if _, err := regexp.Compile(tnt.Spec.NamespaceOptions.ForbiddenLabels.Regex); err != nil {
return ad.Deny(fmt.Sprintf("unable to compile %s regex for forbidden %s", annotation, scope))
return ad.Denyf("unable to compile %s regex for forbidden %s", annotation, scope)
}
}
@@ -5,7 +5,6 @@ package validation
import (
"context"
"fmt"
"strings"
rbacv1 "k8s.io/api/rbac/v1"
@@ -69,7 +68,7 @@ func (h *rbRegexHandler) validate(tnt *capsulev1beta2.Tenant, decoder admission.
if subject.Kind == rbacv1.ServiceAccountKind {
err := validation.IsDNS1123Subdomain(subject.Name)
if len(err) > 0 {
return ad.Deny(fmt.Sprintf("Subject Name '%v' for binding '%v' is invalid. %v", subject.Name, binding.ClusterRoleName, strings.Join(err, ", ")))
return ad.Denyf("Subject Name '%v' for binding '%v' is invalid. %v", subject.Name, binding.ClusterRoleName, strings.Join(err, ", "))
}
}
}
@@ -5,7 +5,6 @@ package validation
import (
"context"
"fmt"
"regexp"
"strings"
@@ -95,9 +94,7 @@ func ValidateRule(tnt *capsulev1beta2.Tenant, req admission.Request) *admission.
if rule.NamespaceSelector != nil {
if _, err := metav1.LabelSelectorAsSelector(rule.NamespaceSelector); err != nil {
return ad.Deny(
fmt.Sprintf("rules[%d].namespaceSelector is invalid: %v", i, err),
)
return ad.Denyf("rules[%d].namespaceSelector is invalid: %v", i, err)
}
}
@@ -105,20 +102,16 @@ func ValidateRule(tnt *capsulev1beta2.Tenant, req admission.Request) *admission.
expr := registry.Expression()
if strings.TrimSpace(expr.Expression) == "" {
return ad.Deny(
fmt.Sprintf("rules[%d].enforce.workloads.registries[%d].exp must not be empty", i, j),
)
return ad.Denyf("rules[%d].enforce.workloads.registries[%d].exp must not be empty", i, j)
}
if _, err := regexp.Compile(expr.Expression); err != nil {
return ad.Deny(
fmt.Sprintf(
"rules[%d].enforce.workloads.registries[%d].exp %q is invalid: %v",
i,
j,
expr.Expression,
err,
),
return ad.Denyf(
"rules[%d].enforce.workloads.registries[%d].exp %q is invalid: %v",
i,
j,
expr.Expression,
err,
)
}
}
@@ -5,7 +5,6 @@ package validation
import (
"context"
"fmt"
"regexp"
"k8s.io/client-go/tools/events"
@@ -69,7 +68,7 @@ func (h *saNameHandler) validateServiceAccountName(tnt *capsulev1beta2.Tenant, r
}
if !compiler.MatchString(owner.Name) {
return ad.Deny(fmt.Sprintf("owner name %s is not a valid Service Account name ", owner.Name))
return ad.Denyf("owner name %s is not a valid Service Account name", owner.Name)
}
}
+6 -6
View File
@@ -39,10 +39,10 @@ func GetNamespaceTenant(
if tnt != nil {
if !validateNamespacePrefix(cfg, ns, tnt) {
return nil, ad.Deny(fmt.Sprintf(
return nil, ad.Denyf(
"The Namespace name must start with '%s-' when ForceTenantPrefix is enabled in the Tenant.",
tnt.GetName(),
))
)
}
return tnt, nil
@@ -65,10 +65,10 @@ func GetNamespaceTenant(
if len(tnts) == 1 {
if !validateNamespacePrefix(cfg, ns, &tnts[0]) {
return nil, ad.Deny(fmt.Sprintf(
return nil, ad.Denyf(
"The Namespace name must start with '%s-' when ForceTenantPrefix is enabled in the Tenant.",
tnts[0].GetName(),
))
)
}
return &tnts[0], nil
@@ -81,10 +81,10 @@ func GetNamespaceTenant(
if tnt != nil {
if !validateNamespacePrefix(cfg, ns, tnt) {
return nil, ad.Deny(fmt.Sprintf(
return nil, ad.Denyf(
"The Namespace name must start with '%s-' when ForceTenantPrefix is enabled in the Tenant.",
tnt.GetName(),
))
)
}
return tnt, nil
+1 -3
View File
@@ -10,7 +10,5 @@ import (
)
func ErroredResponse(err error) *admission.Response {
response := admission.Errored(http.StatusInternalServerError, err)
return &response
return new(admission.Errored(http.StatusInternalServerError, err))
}
+11 -6
View File
@@ -4,22 +4,27 @@
package admission
import (
"fmt"
"path"
"strings"
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
)
func Deny(message string) *admission.Response {
response := admission.Denied(message)
func Denyf(message string, args ...any) *admission.Response {
return Deny(fmt.Sprintf(message, args...))
}
return &response
func Deny(message string) *admission.Response {
return new(admission.Denied(message))
}
func Allowf(message string, args ...any) *admission.Response {
return Allow(fmt.Sprintf(message, args...))
}
func Allow(message string) *admission.Response {
response := admission.Allowed(message)
return &response
return new(admission.Allowed(message))
}
func normalizePath(p string) string {