mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-18 04:54:54 +00:00
Refactor: Centralize declarative validation and migration logic
The boilerplate for running declarative validation was duplicated across multiple resource strategies. This included feature gate checks, metric identifier generation, error comparison, and conditional merging logic, which made the code verbose and difficult to maintain. This commit introduces a new helper function, `rest.ValidateDeclarativelyWithMigrationChecks`, to encapsulate this common logic. All relevant strategies have been refactored to use this new function, resulting in cleaner and more concise code.
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@@ -29,11 +30,9 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
|
||||
)
|
||||
|
||||
@@ -116,25 +115,7 @@ func (csrStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object
|
||||
func (csrStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||
csr := obj.(*certificates.CertificateSigningRequest)
|
||||
allErrs := validation.ValidateCertificateSigningRequestCreate(csr)
|
||||
|
||||
// If DeclarativeValidation feature gate is enabled, also run declarative validation
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
|
||||
const validationIdentifier = "csr_create"
|
||||
// Run declarative validation with panic recovery
|
||||
declarativeErrs := rest.ValidateDeclaratively(ctx, legacyscheme.Scheme, csr, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
|
||||
// Compare imperative and declarative errors and log + emit metric if there's a mismatch
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, allErrs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
allErrs = append(allErrs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, csr, nil, allErrs, operation.Create)
|
||||
}
|
||||
|
||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||
@@ -148,25 +129,7 @@ func (csrStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object)
|
||||
oldCSR := old.(*certificates.CertificateSigningRequest)
|
||||
newCSR := obj.(*certificates.CertificateSigningRequest)
|
||||
errs := validation.ValidateCertificateSigningRequestUpdate(newCSR, oldCSR)
|
||||
// If DeclarativeValidation feature gate is enabled, also run declarative validation
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
|
||||
const validationIdentifier = "csr_update"
|
||||
// Run declarative update validation with panic recovery
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newCSR, oldCSR, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
|
||||
// Compare imperative and declarative errors and emit metric if there's a mismatch
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newCSR, oldCSR, errs, operation.Update)
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
@@ -282,23 +245,7 @@ func (csrStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob
|
||||
newCSR := obj.(*certificates.CertificateSigningRequest)
|
||||
oldCSR := old.(*certificates.CertificateSigningRequest)
|
||||
errs := validation.ValidateCertificateSigningRequestStatusUpdate(newCSR, oldCSR)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
|
||||
const validationIdentifier = "csr_status_update"
|
||||
// Run declarative update validation with panic recovery
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newCSR, oldCSR, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
|
||||
// Compare imperative and declarative errors and emit metric if there's a mismatch
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newCSR, oldCSR, errs, operation.Update)
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
@@ -353,23 +300,7 @@ func (csrApprovalStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.
|
||||
newCSR := obj.(*certificates.CertificateSigningRequest)
|
||||
oldCSR := old.(*certificates.CertificateSigningRequest)
|
||||
errs := validation.ValidateCertificateSigningRequestApprovalUpdate(newCSR, oldCSR)
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
|
||||
const validationIdentifier = "csr_approval_update"
|
||||
// Run declarative update validation with panic recovery
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newCSR, oldCSR, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
|
||||
// Compare imperative and declarative errors and emit metric if there's a mismatch
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newCSR, oldCSR, errs, operation.Update)
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
|
||||
@@ -23,6 +23,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -32,7 +33,6 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling"
|
||||
@@ -40,7 +40,6 @@ import (
|
||||
"k8s.io/kubernetes/pkg/apis/autoscaling/validation"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
"k8s.io/kubernetes/pkg/printers"
|
||||
printersinternal "k8s.io/kubernetes/pkg/printers/internalversion"
|
||||
printerstorage "k8s.io/kubernetes/pkg/printers/storage"
|
||||
@@ -315,25 +314,7 @@ func (i *scaleUpdatedObjectInfo) UpdatedObject(ctx context.Context, oldObj runti
|
||||
}
|
||||
|
||||
errs := validation.ValidateScale(scale)
|
||||
|
||||
// If DeclarativeValidation feature gate is enabled, also run declarative validation
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
|
||||
const validationIdentifier = "rc_scale"
|
||||
// Run declarative validation with panic recovery
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(
|
||||
ctx, legacyscheme.Scheme, scale, oldScale, rest.WithTakeover(takeover), rest.WithSubresourceMapper(i.scaleGVKMapper), rest.WithValidationIdentifier(validationIdentifier))
|
||||
|
||||
// Compare imperative and declarative errors and log + emit metric if there's a mismatch
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
errs = rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, scale, oldScale, errs, operation.Update, rest.WithSubresourceMapper(i.scaleGVKMapper))
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.NewInvalid(autoscaling.Kind("Scale"), replicationcontroller.Name, errs)
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -39,13 +40,11 @@ import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
apistorage "k8s.io/apiserver/pkg/storage"
|
||||
"k8s.io/apiserver/pkg/storage/names"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/kubernetes/pkg/api/legacyscheme"
|
||||
"k8s.io/kubernetes/pkg/api/pod"
|
||||
api "k8s.io/kubernetes/pkg/apis/core"
|
||||
"k8s.io/kubernetes/pkg/apis/core/helper"
|
||||
corevalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// rcStrategy implements verification logic for Replication Controllers.
|
||||
@@ -129,25 +128,7 @@ func (rcStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorL
|
||||
|
||||
// Run imperative validation
|
||||
allErrs := corevalidation.ValidateReplicationController(controller, opts)
|
||||
|
||||
// If DeclarativeValidation feature gate is enabled, also run declarative validation
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
const validationIdentifier = "rc_create"
|
||||
|
||||
// Run declarative validation with panic recovery
|
||||
declarativeErrs := rest.ValidateDeclaratively(ctx, legacyscheme.Scheme, controller, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
|
||||
// Compare imperative and declarative errors and log + emit metric if there's a mismatch
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, allErrs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
allErrs = append(allErrs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, controller, nil, allErrs, operation.Create)
|
||||
}
|
||||
|
||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||
@@ -199,25 +180,7 @@ func (rcStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) f
|
||||
}
|
||||
}
|
||||
|
||||
// If DeclarativeValidation feature gate is enabled, also run declarative validation
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
const validationIdentifier = "rc_update"
|
||||
|
||||
// Run declarative update validation with panic recovery
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newRc, oldRc, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
|
||||
// Compare imperative and declarative errors and emit metric if there's a mismatch
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
|
||||
return errs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newRc, oldRc, errs, operation.Update)
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"context"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
@@ -53,16 +54,7 @@ func (deviceClassStrategy) Validate(ctx context.Context, obj runtime.Object) fie
|
||||
deviceClass := obj.(*resource.DeviceClass)
|
||||
errorList := validation.ValidateDeviceClass(deviceClass)
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
declarativeErrs := rest.ValidateDeclaratively(ctx, legacyscheme.Scheme, deviceClass, rest.WithTakeover(takeover))
|
||||
validationIdentifier := "deviceclass_create"
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errorList, declarativeErrs, takeover, validationIdentifier)
|
||||
if takeover {
|
||||
errorList = append(errorList.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return errorList
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, deviceClass, nil, errorList, operation.Create)
|
||||
}
|
||||
|
||||
func (deviceClassStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
|
||||
@@ -95,16 +87,7 @@ func (deviceClassStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.
|
||||
errorList := validation.ValidateDeviceClass(newClass)
|
||||
errorList = append(errorList, validation.ValidateDeviceClassUpdate(newClass, oldClass)...)
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newClass, oldClass, rest.WithTakeover(takeover))
|
||||
validationIdentifier := "deviceclass_update"
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errorList, declarativeErrs, takeover, validationIdentifier)
|
||||
if takeover {
|
||||
errorList = append(errorList.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return errorList
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newClass, oldClass, errorList, operation.Update)
|
||||
}
|
||||
|
||||
func (deviceClassStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||
|
||||
@@ -22,6 +22,7 @@ import (
|
||||
|
||||
"sigs.k8s.io/structured-merge-diff/v6/fieldpath"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
@@ -99,17 +100,7 @@ func (s *resourceclaimStrategy) Validate(ctx context.Context, obj runtime.Object
|
||||
|
||||
allErrs := resourceutils.AuthorizedForAdmin(ctx, claim.Spec.Devices.Requests, claim.Namespace, s.nsClient)
|
||||
allErrs = append(allErrs, validation.ValidateResourceClaim(claim)...)
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
const validationIdentifier = "resourceclaim_create"
|
||||
declarativeErrs := rest.ValidateDeclaratively(ctx, legacyscheme.Scheme, claim, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, allErrs, declarativeErrs, takeover, validationIdentifier)
|
||||
if takeover {
|
||||
allErrs = append(allErrs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, claim, nil, allErrs, operation.Create)
|
||||
}
|
||||
|
||||
func (*resourceclaimStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string {
|
||||
@@ -137,17 +128,7 @@ func (s *resourceclaimStrategy) ValidateUpdate(ctx context.Context, obj, old run
|
||||
// AuthorizedForAdmin isn't needed here because the spec is immutable.
|
||||
errorList := validation.ValidateResourceClaim(newClaim)
|
||||
errorList = append(errorList, validation.ValidateResourceClaimUpdate(newClaim, oldClaim)...)
|
||||
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
const validationIdentifier = "resourceclaim_update"
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newClaim, oldClaim, rest.WithTakeover(takeover), rest.WithValidationIdentifier(validationIdentifier))
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errorList, declarativeErrs, takeover, validationIdentifier)
|
||||
if takeover {
|
||||
errorList = append(errorList.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return errorList
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newClaim, oldClaim, errorList, operation.Update)
|
||||
}
|
||||
|
||||
func (*resourceclaimStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||
@@ -210,24 +191,7 @@ func (r *resourceclaimStatusStrategy) ValidateUpdate(ctx context.Context, obj, o
|
||||
}
|
||||
errs := resourceutils.AuthorizedForAdminStatus(ctx, newAllocationResult, oldAllocationResult, newClaim.Namespace, r.nsClient)
|
||||
errs = append(errs, validation.ValidateResourceClaimStatusUpdate(newClaim, oldClaim)...)
|
||||
|
||||
// If DeclarativeValidation feature gate is enabled, also run declarative validation
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
// Determine if takeover is enabled
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
|
||||
// Run declarative update validation with panic recovery
|
||||
declarativeErrs := rest.ValidateUpdateDeclaratively(ctx, legacyscheme.Scheme, newClaim, oldClaim, rest.WithTakeover(takeover))
|
||||
// Compare imperative and declarative errors and emit metric if there's a mismatch
|
||||
const validationIdentifier = "resourceclaim_status_update"
|
||||
rest.CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
// Only apply declarative errors if takeover is enabled
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
}
|
||||
return errs
|
||||
return rest.ValidateDeclarativelyWithMigrationChecks(ctx, legacyscheme.Scheme, newClaim, oldClaim, errs, operation.Update)
|
||||
}
|
||||
|
||||
// WarningsOnUpdate returns warnings for the given update.
|
||||
|
||||
@@ -23,13 +23,14 @@ import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/operation"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
genericapirequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
validationmetrics "k8s.io/apiserver/pkg/validation"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
)
|
||||
|
||||
// ValidationConfig defines how a declarative validation request may be configured.
|
||||
@@ -330,6 +331,78 @@ func panicSafeValidateFunc(
|
||||
}
|
||||
}
|
||||
|
||||
func metricIdentifier(ctx context.Context, obj runtime.Object, opType operation.Type) (string, error) {
|
||||
var identifier string
|
||||
var err error
|
||||
|
||||
identifier = "unknown_resource"
|
||||
// Use kind for identifier.
|
||||
if obj != nil {
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
if gvk.Kind != "" {
|
||||
identifier = strings.ToLower(gvk.Kind)
|
||||
}
|
||||
}
|
||||
|
||||
// Use requestInfo for subresource.
|
||||
requestInfo, found := genericapirequest.RequestInfoFrom(ctx)
|
||||
if !found {
|
||||
err = fmt.Errorf("could not find requestInfo in context")
|
||||
} else if len(requestInfo.Subresource) > 0 {
|
||||
// subresource can be a path, so replace '/' with '_'
|
||||
identifier += "_" + strings.ReplaceAll(requestInfo.Subresource, "/", "_")
|
||||
}
|
||||
|
||||
switch opType {
|
||||
case operation.Create:
|
||||
identifier += "_create"
|
||||
case operation.Update:
|
||||
identifier += "_update"
|
||||
default:
|
||||
if err == nil {
|
||||
err = fmt.Errorf("unknown operation type: %v", opType)
|
||||
}
|
||||
identifier += "_unknown_op"
|
||||
}
|
||||
return identifier, err
|
||||
}
|
||||
|
||||
// ValidateDeclarativelyWithMigrationChecks is a helper function that encapsulates the logic for running declarative validation.
|
||||
// It checks if the DeclarativeValidation feature gate is enabled, generates a validation identifier,
|
||||
// runs declarative validation, compares the results with imperative validation, and merges the errors if takeover is enabled.
|
||||
func ValidateDeclarativelyWithMigrationChecks(ctx context.Context, scheme *runtime.Scheme, obj, oldObj runtime.Object, errs field.ErrorList, opType operation.Type, configOpts ...ValidationConfig) field.ErrorList {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidation) {
|
||||
return errs
|
||||
}
|
||||
|
||||
takeover := utilfeature.DefaultFeatureGate.Enabled(features.DeclarativeValidationTakeover)
|
||||
|
||||
validationIdentifier, err := metricIdentifier(ctx, obj, opType)
|
||||
if err != nil {
|
||||
// Log the error, but continue with the best-effort identifier.
|
||||
klog.FromContext(ctx).Error(err, "failed to generate complete validation identifier for declarative validation")
|
||||
}
|
||||
|
||||
// Directly create the config and call the core validation logic.
|
||||
cfg := &validationConfigOption{opType: opType}
|
||||
opts := []ValidationConfig{WithTakeover(takeover), WithValidationIdentifier(validationIdentifier)}
|
||||
opts = append(opts, configOpts...)
|
||||
for _, o := range opts {
|
||||
o(cfg)
|
||||
}
|
||||
|
||||
// Call the panic-safe wrapper with the real validation function.
|
||||
declarativeErrs := panicSafeValidateFunc(validateDeclaratively, cfg.takeover, cfg.validationIdentifier)(ctx, scheme, obj, oldObj, cfg)
|
||||
|
||||
CompareDeclarativeErrorsAndEmitMismatches(ctx, errs, declarativeErrs, takeover, validationIdentifier)
|
||||
|
||||
if takeover {
|
||||
errs = append(errs.RemoveCoveredByDeclarative(), declarativeErrs...)
|
||||
}
|
||||
|
||||
return errs
|
||||
}
|
||||
|
||||
// RecordDuplicateValidationErrors increments a metric and log the error when duplicate validation errors are found.
|
||||
func RecordDuplicateValidationErrors(ctx context.Context, qualifiedKind schema.GroupKind, errs field.ErrorList) {
|
||||
logger := klog.FromContext(ctx)
|
||||
|
||||
Reference in New Issue
Block a user