mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-22 19:31:44 +00:00
Merge pull request #113699 from liggitt/manjusaka/fix-107415
Add extra value validation for matchExpression field in LabelSelector
This commit is contained in:
commit
f2c89045f4
@ -413,6 +413,7 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
|||||||
AllowIndivisibleHugePagesValues: false,
|
AllowIndivisibleHugePagesValues: false,
|
||||||
// Allow pod spec with expanded DNS configuration
|
// Allow pod spec with expanded DNS configuration
|
||||||
AllowExpandedDNSConfig: utilfeature.DefaultFeatureGate.Enabled(features.ExpandedDNSConfig) || haveSameExpandedDNSConfig(podSpec, oldPodSpec),
|
AllowExpandedDNSConfig: utilfeature.DefaultFeatureGate.Enabled(features.ExpandedDNSConfig) || haveSameExpandedDNSConfig(podSpec, oldPodSpec),
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
if oldPodSpec != nil {
|
if oldPodSpec != nil {
|
||||||
@ -429,6 +430,8 @@ func GetValidationOptionsFromPodSpecAndMeta(podSpec, oldPodSpec *api.PodSpec, po
|
|||||||
// if old spec used non-integer multiple of huge page unit size, we must allow it
|
// if old spec used non-integer multiple of huge page unit size, we must allow it
|
||||||
opts.AllowIndivisibleHugePagesValues = usesIndivisibleHugePagesValues(oldPodSpec)
|
opts.AllowIndivisibleHugePagesValues = usesIndivisibleHugePagesValues(oldPodSpec)
|
||||||
|
|
||||||
|
opts.AllowInvalidLabelValueInSelector = hasInvalidLabelValueInAffinitySelector(oldPodSpec)
|
||||||
|
|
||||||
}
|
}
|
||||||
if oldPodMeta != nil && !opts.AllowInvalidPodDeletionCost {
|
if oldPodMeta != nil && !opts.AllowInvalidPodDeletionCost {
|
||||||
// This is an update, so validate only if the existing object was valid.
|
// This is an update, so validate only if the existing object was valid.
|
||||||
@ -756,3 +759,37 @@ func SeccompAnnotationForField(field *api.SeccompProfile) string {
|
|||||||
// type is specified
|
// type is specified
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasInvalidLabelValueInAffinitySelector(spec *api.PodSpec) bool {
|
||||||
|
if spec.Affinity != nil {
|
||||||
|
if spec.Affinity.PodAffinity != nil {
|
||||||
|
for _, term := range spec.Affinity.PodAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
|
||||||
|
allErrs := apivalidation.ValidatePodAffinityTermSelector(term, false, nil)
|
||||||
|
if len(allErrs) != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, term := range spec.Affinity.PodAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
|
||||||
|
allErrs := apivalidation.ValidatePodAffinityTermSelector(term.PodAffinityTerm, false, nil)
|
||||||
|
if len(allErrs) != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if spec.Affinity.PodAntiAffinity != nil {
|
||||||
|
for _, term := range spec.Affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution {
|
||||||
|
allErrs := apivalidation.ValidatePodAffinityTermSelector(term, false, nil)
|
||||||
|
if len(allErrs) != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, term := range spec.Affinity.PodAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution {
|
||||||
|
allErrs := apivalidation.ValidatePodAffinityTermSelector(term.PodAffinityTerm, false, nil)
|
||||||
|
if len(allErrs) != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -210,6 +210,7 @@ func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingW
|
|||||||
requireNoSideEffects: true,
|
requireNoSideEffects: true,
|
||||||
requireRecognizedAdmissionReviewVersion: true,
|
requireRecognizedAdmissionReviewVersion: true,
|
||||||
requireUniqueWebhookNames: true,
|
requireUniqueWebhookNames: true,
|
||||||
|
allowInvalidLabelValueInSelector: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -236,6 +237,7 @@ func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebho
|
|||||||
requireNoSideEffects: true,
|
requireNoSideEffects: true,
|
||||||
requireRecognizedAdmissionReviewVersion: true,
|
requireRecognizedAdmissionReviewVersion: true,
|
||||||
requireUniqueWebhookNames: true,
|
requireUniqueWebhookNames: true,
|
||||||
|
allowInvalidLabelValueInSelector: false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,10 +245,12 @@ type validationOptions struct {
|
|||||||
requireNoSideEffects bool
|
requireNoSideEffects bool
|
||||||
requireRecognizedAdmissionReviewVersion bool
|
requireRecognizedAdmissionReviewVersion bool
|
||||||
requireUniqueWebhookNames bool
|
requireUniqueWebhookNames bool
|
||||||
|
allowInvalidLabelValueInSelector bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, opts validationOptions) field.ErrorList {
|
func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, opts validationOptions) field.ErrorList {
|
||||||
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata"))
|
||||||
|
|
||||||
hookNames := sets.NewString()
|
hookNames := sets.NewString()
|
||||||
for i, hook := range e.Webhooks {
|
for i, hook := range e.Webhooks {
|
||||||
allErrors = append(allErrors, validateMutatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...)
|
allErrors = append(allErrors, validateMutatingWebhook(&hook, opts, field.NewPath("webhooks").Index(i))...)
|
||||||
@ -266,6 +270,9 @@ func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, op
|
|||||||
var allErrors field.ErrorList
|
var allErrors field.ErrorList
|
||||||
// hook.Name must be fully qualified
|
// hook.Name must be fully qualified
|
||||||
allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
|
allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
|
||||||
|
labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
|
||||||
for i, rule := range hook.Rules {
|
for i, rule := range hook.Rules {
|
||||||
allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
|
allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
|
||||||
@ -291,11 +298,11 @@ func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, op
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hook.NamespaceSelector != nil {
|
if hook.NamespaceSelector != nil {
|
||||||
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
|
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if hook.ObjectSelector != nil {
|
if hook.ObjectSelector != nil {
|
||||||
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...)
|
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cc := hook.ClientConfig
|
cc := hook.ClientConfig
|
||||||
@ -314,6 +321,9 @@ func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts v
|
|||||||
var allErrors field.ErrorList
|
var allErrors field.ErrorList
|
||||||
// hook.Name must be fully qualified
|
// hook.Name must be fully qualified
|
||||||
allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
|
allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...)
|
||||||
|
labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.allowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
|
||||||
for i, rule := range hook.Rules {
|
for i, rule := range hook.Rules {
|
||||||
allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
|
allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...)
|
||||||
@ -339,10 +349,10 @@ func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts v
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hook.NamespaceSelector != nil {
|
if hook.NamespaceSelector != nil {
|
||||||
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
|
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, fldPath.Child("namespaceSelector"))...)
|
||||||
}
|
}
|
||||||
if hook.ObjectSelector != nil {
|
if hook.ObjectSelector != nil {
|
||||||
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...)
|
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, fldPath.Child("objectSelector"))...)
|
||||||
}
|
}
|
||||||
if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) {
|
if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) {
|
||||||
allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List()))
|
allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List()))
|
||||||
@ -508,12 +518,55 @@ func validatingHasNoSideEffects(webhooks []admissionregistration.ValidatingWebho
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooksallow invalid label value in selector
|
||||||
|
func validatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.ValidatingWebhook) bool {
|
||||||
|
labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hook := range webhooks {
|
||||||
|
if hook.NamespaceSelector != nil {
|
||||||
|
if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hook.ObjectSelector != nil {
|
||||||
|
if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// mutatingWebhookAllowInvalidLabelValueInSelector returns true if all webhooks allow invalid label value in selector
|
||||||
|
func mutatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistration.MutatingWebhook) bool {
|
||||||
|
labelSelectorValidationOpts := metav1validation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, hook := range webhooks {
|
||||||
|
if hook.NamespaceSelector != nil {
|
||||||
|
if len(metav1validation.ValidateLabelSelector(hook.NamespaceSelector, labelSelectorValidationOpts, nil)) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hook.ObjectSelector != nil {
|
||||||
|
if len(metav1validation.ValidateLabelSelector(hook.ObjectSelector, labelSelectorValidationOpts, nil)) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateValidatingWebhookConfigurationUpdate validates update of validating webhook configuration
|
// ValidateValidatingWebhookConfigurationUpdate validates update of validating webhook configuration
|
||||||
func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
|
func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
|
||||||
return validateValidatingWebhookConfiguration(newC, validationOptions{
|
return validateValidatingWebhookConfiguration(newC, validationOptions{
|
||||||
requireNoSideEffects: validatingHasNoSideEffects(oldC.Webhooks),
|
requireNoSideEffects: validatingHasNoSideEffects(oldC.Webhooks),
|
||||||
requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
||||||
requireUniqueWebhookNames: validatingHasUniqueWebhookNames(oldC.Webhooks),
|
requireUniqueWebhookNames: validatingHasUniqueWebhookNames(oldC.Webhooks),
|
||||||
|
allowInvalidLabelValueInSelector: validatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -523,6 +576,7 @@ func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistratio
|
|||||||
requireNoSideEffects: mutatingHasNoSideEffects(oldC.Webhooks),
|
requireNoSideEffects: mutatingHasNoSideEffects(oldC.Webhooks),
|
||||||
requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
||||||
requireUniqueWebhookNames: mutatingHasUniqueWebhookNames(oldC.Webhooks),
|
requireUniqueWebhookNames: mutatingHasUniqueWebhookNames(oldC.Webhooks),
|
||||||
|
allowInvalidLabelValueInSelector: mutatingWebhookHasInvalidLabelValueInSelector(oldC.Webhooks),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -634,13 +688,15 @@ func validateMatchResources(mc *admissionregistration.MatchResources, fldPath *f
|
|||||||
if mc.NamespaceSelector == nil {
|
if mc.NamespaceSelector == nil {
|
||||||
allErrors = append(allErrors, field.Required(fldPath.Child("namespaceSelector"), ""))
|
allErrors = append(allErrors, field.Required(fldPath.Child("namespaceSelector"), ""))
|
||||||
} else {
|
} else {
|
||||||
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
|
// validate selector strictly, this type was released after issue #99139 was resolved
|
||||||
|
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.NamespaceSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("namespaceSelector"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if mc.ObjectSelector == nil {
|
if mc.ObjectSelector == nil {
|
||||||
allErrors = append(allErrors, field.Required(fldPath.Child("labelSelector"), ""))
|
allErrors = append(allErrors, field.Required(fldPath.Child("labelSelector"), ""))
|
||||||
} else {
|
} else {
|
||||||
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.ObjectSelector, fldPath.Child("labelSelector"))...)
|
// validate selector strictly, this type was released after issue #99139 was resolved
|
||||||
|
allErrors = append(allErrors, metav1validation.ValidateLabelSelector(mc.ObjectSelector, metav1validation.LabelSelectorValidationOptions{}, fldPath.Child("labelSelector"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, namedRuleWithOperations := range mc.ResourceRules {
|
for i, namedRuleWithOperations := range mc.ResourceRules {
|
||||||
|
@ -132,7 +132,8 @@ func ValidateStatefulSetSpec(spec *apps.StatefulSetSpec, fldPath *field.Path, op
|
|||||||
if spec.Selector == nil {
|
if spec.Selector == nil {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
||||||
} else {
|
} else {
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
// validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...)
|
||||||
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
|
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for statefulset"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for statefulset"))
|
||||||
}
|
}
|
||||||
@ -336,8 +337,9 @@ func ValidateDaemonSetStatusUpdate(ds, oldDS *apps.DaemonSet) field.ErrorList {
|
|||||||
// ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set.
|
// ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set.
|
||||||
func ValidateDaemonSetSpec(spec *apps.DaemonSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList {
|
func ValidateDaemonSetSpec(spec *apps.DaemonSetSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector}
|
||||||
|
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...)
|
||||||
|
|
||||||
selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
|
selector, err := metav1.LabelSelectorAsSelector(spec.Selector)
|
||||||
if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) {
|
if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) {
|
||||||
@ -540,7 +542,8 @@ func ValidateDeploymentSpec(spec *apps.DeploymentSpec, fldPath *field.Path, opts
|
|||||||
if spec.Selector == nil {
|
if spec.Selector == nil {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
||||||
} else {
|
} else {
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
// validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...)
|
||||||
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
|
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
|
||||||
}
|
}
|
||||||
@ -702,7 +705,8 @@ func ValidateReplicaSetSpec(spec *apps.ReplicaSetSpec, fldPath *field.Path, opts
|
|||||||
if spec.Selector == nil {
|
if spec.Selector == nil {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
||||||
} else {
|
} else {
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
// validate selector strictly, spec.selector was always required to pass LabelSelectorAsSelector below
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}, fldPath.Child("selector"))...)
|
||||||
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
|
if len(spec.Selector.MatchLabels)+len(spec.Selector.MatchExpressions) == 0 {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("selector"), spec.Selector, "empty selector is invalid for deployment"))
|
||||||
}
|
}
|
||||||
|
@ -146,11 +146,13 @@ func hasJobTrackingAnnotation(job *batch.Job) bool {
|
|||||||
// ValidateJobSpec validates a JobSpec and returns an ErrorList with any errors.
|
// ValidateJobSpec validates a JobSpec and returns an ErrorList with any errors.
|
||||||
func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList {
|
func ValidateJobSpec(spec *batch.JobSpec, fldPath *field.Path, opts apivalidation.PodValidationOptions) field.ErrorList {
|
||||||
allErrs := validateJobSpec(spec, fldPath, opts)
|
allErrs := validateJobSpec(spec, fldPath, opts)
|
||||||
|
|
||||||
if spec.Selector == nil {
|
if spec.Selector == nil {
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
allErrs = append(allErrs, field.Required(fldPath.Child("selector"), ""))
|
||||||
} else {
|
} else {
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Whether manually or automatically generated, the selector of the job must match the pods it will produce.
|
// Whether manually or automatically generated, the selector of the job must match the pods it will produce.
|
||||||
|
@ -2029,6 +2029,8 @@ type PersistentVolumeClaimSpecValidationOptions struct {
|
|||||||
EnableRecoverFromExpansionFailure bool
|
EnableRecoverFromExpansionFailure bool
|
||||||
// Allow assigning StorageClass to unbound PVCs retroactively
|
// Allow assigning StorageClass to unbound PVCs retroactively
|
||||||
EnableRetroactiveDefaultStorageClass bool
|
EnableRetroactiveDefaultStorageClass bool
|
||||||
|
// Allow to validate the label value of the label selector
|
||||||
|
AllowInvalidLabelValueInSelector bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
|
func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolumeClaim) PersistentVolumeClaimSpecValidationOptions {
|
||||||
@ -2036,11 +2038,19 @@ func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolum
|
|||||||
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||||
EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure),
|
EnableRecoverFromExpansionFailure: utilfeature.DefaultFeatureGate.Enabled(features.RecoverVolumeExpansionFailure),
|
||||||
EnableRetroactiveDefaultStorageClass: utilfeature.DefaultFeatureGate.Enabled(features.RetroactiveDefaultStorageClass),
|
EnableRetroactiveDefaultStorageClass: utilfeature.DefaultFeatureGate.Enabled(features.RetroactiveDefaultStorageClass),
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
}
|
}
|
||||||
if oldPvc == nil {
|
if oldPvc == nil {
|
||||||
// If there's no old PVC, use the options based solely on feature enablement
|
// If there's no old PVC, use the options based solely on feature enablement
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
if len(unversionedvalidation.ValidateLabelSelector(oldPvc.Spec.Selector, labelSelectorValidationOpts, nil)) > 0 {
|
||||||
|
// If the old object had an invalid label selector, continue to allow it in the new object
|
||||||
|
opts.AllowInvalidLabelValueInSelector = true
|
||||||
|
}
|
||||||
|
|
||||||
if helper.ContainsAccessMode(oldPvc.Spec.AccessModes, core.ReadWriteOncePod) {
|
if helper.ContainsAccessMode(oldPvc.Spec.AccessModes, core.ReadWriteOncePod) {
|
||||||
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
||||||
@ -2052,11 +2062,19 @@ func ValidationOptionsForPersistentVolumeClaim(pvc, oldPvc *core.PersistentVolum
|
|||||||
func ValidationOptionsForPersistentVolumeClaimTemplate(claimTemplate, oldClaimTemplate *core.PersistentVolumeClaimTemplate) PersistentVolumeClaimSpecValidationOptions {
|
func ValidationOptionsForPersistentVolumeClaimTemplate(claimTemplate, oldClaimTemplate *core.PersistentVolumeClaimTemplate) PersistentVolumeClaimSpecValidationOptions {
|
||||||
opts := PersistentVolumeClaimSpecValidationOptions{
|
opts := PersistentVolumeClaimSpecValidationOptions{
|
||||||
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
AllowReadWriteOncePod: utilfeature.DefaultFeatureGate.Enabled(features.ReadWriteOncePod),
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
}
|
}
|
||||||
if oldClaimTemplate == nil {
|
if oldClaimTemplate == nil {
|
||||||
// If there's no old PVC template, use the options based solely on feature enablement
|
// If there's no old PVC template, use the options based solely on feature enablement
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
if len(unversionedvalidation.ValidateLabelSelector(oldClaimTemplate.Spec.Selector, labelSelectorValidationOpts, nil)) > 0 {
|
||||||
|
// If the old object had an invalid label selector, continue to allow it in the new object
|
||||||
|
opts.AllowInvalidLabelValueInSelector = true
|
||||||
|
}
|
||||||
if helper.ContainsAccessMode(oldClaimTemplate.Spec.AccessModes, core.ReadWriteOncePod) {
|
if helper.ContainsAccessMode(oldClaimTemplate.Spec.AccessModes, core.ReadWriteOncePod) {
|
||||||
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
// If the old object allowed "ReadWriteOncePod", continue to allow it in the new object
|
||||||
opts.AllowReadWriteOncePod = true
|
opts.AllowReadWriteOncePod = true
|
||||||
@ -2099,7 +2117,10 @@ func ValidatePersistentVolumeClaimSpec(spec *core.PersistentVolumeClaimSpec, fld
|
|||||||
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required"))
|
allErrs = append(allErrs, field.Required(fldPath.Child("accessModes"), "at least 1 access mode is required"))
|
||||||
}
|
}
|
||||||
if spec.Selector != nil {
|
if spec.Selector != nil {
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOpts, fldPath.Child("selector"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
expandedSupportedAccessModes := sets.StringKeySet(supportedAccessModes)
|
expandedSupportedAccessModes := sets.StringKeySet(supportedAccessModes)
|
||||||
@ -3371,7 +3392,7 @@ func validateImagePullSecrets(imagePullSecrets []core.LocalObjectReference, fldP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validateAffinity checks if given affinities are valid
|
// validateAffinity checks if given affinities are valid
|
||||||
func validateAffinity(affinity *core.Affinity, fldPath *field.Path) field.ErrorList {
|
func validateAffinity(affinity *core.Affinity, opts PodValidationOptions, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if affinity != nil {
|
if affinity != nil {
|
||||||
@ -3379,10 +3400,10 @@ func validateAffinity(affinity *core.Affinity, fldPath *field.Path) field.ErrorL
|
|||||||
allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, fldPath.Child("nodeAffinity"))...)
|
allErrs = append(allErrs, validateNodeAffinity(affinity.NodeAffinity, fldPath.Child("nodeAffinity"))...)
|
||||||
}
|
}
|
||||||
if affinity.PodAffinity != nil {
|
if affinity.PodAffinity != nil {
|
||||||
allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, fldPath.Child("podAffinity"))...)
|
allErrs = append(allErrs, validatePodAffinity(affinity.PodAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAffinity"))...)
|
||||||
}
|
}
|
||||||
if affinity.PodAntiAffinity != nil {
|
if affinity.PodAntiAffinity != nil {
|
||||||
allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, fldPath.Child("podAntiAffinity"))...)
|
allErrs = append(allErrs, validatePodAntiAffinity(affinity.PodAntiAffinity, opts.AllowInvalidLabelValueInSelector, fldPath.Child("podAntiAffinity"))...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3543,6 +3564,8 @@ type PodValidationOptions struct {
|
|||||||
AllowDownwardAPIHugePages bool
|
AllowDownwardAPIHugePages bool
|
||||||
// Allow invalid pod-deletion-cost annotation value for backward compatibility.
|
// Allow invalid pod-deletion-cost annotation value for backward compatibility.
|
||||||
AllowInvalidPodDeletionCost bool
|
AllowInvalidPodDeletionCost bool
|
||||||
|
// Allow invalid label-value in LabelSelector
|
||||||
|
AllowInvalidLabelValueInSelector bool
|
||||||
// Allow pod spec to use non-integer multiple of huge page unit size
|
// Allow pod spec to use non-integer multiple of huge page unit size
|
||||||
AllowIndivisibleHugePagesValues bool
|
AllowIndivisibleHugePagesValues bool
|
||||||
// Allow more DNSSearchPaths and longer DNSSearchListChars
|
// Allow more DNSSearchPaths and longer DNSSearchListChars
|
||||||
@ -3646,7 +3669,7 @@ func ValidatePodSpec(spec *core.PodSpec, podMeta *metav1.ObjectMeta, fldPath *fi
|
|||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabels(spec.NodeSelector, fldPath.Child("nodeSelector"))...)
|
||||||
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...)
|
allErrs = append(allErrs, ValidatePodSecurityContext(spec.SecurityContext, spec, fldPath, fldPath.Child("securityContext"), opts)...)
|
||||||
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
|
allErrs = append(allErrs, validateImagePullSecrets(spec.ImagePullSecrets, fldPath.Child("imagePullSecrets"))...)
|
||||||
allErrs = append(allErrs, validateAffinity(spec.Affinity, fldPath.Child("affinity"))...)
|
allErrs = append(allErrs, validateAffinity(spec.Affinity, opts, fldPath.Child("affinity"))...)
|
||||||
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
|
allErrs = append(allErrs, validatePodDNSConfig(spec.DNSConfig, &spec.DNSPolicy, fldPath.Child("dnsConfig"), opts)...)
|
||||||
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
|
allErrs = append(allErrs, validateReadinessGates(spec.ReadinessGates, fldPath.Child("readinessGates"))...)
|
||||||
allErrs = append(allErrs, validateSchedulingGates(spec.SchedulingGates, fldPath.Child("schedulingGates"))...)
|
allErrs = append(allErrs, validateSchedulingGates(spec.SchedulingGates, fldPath.Child("schedulingGates"))...)
|
||||||
@ -4010,12 +4033,10 @@ func ValidatePreferredSchedulingTerms(terms []core.PreferredSchedulingTerm, fldP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data
|
// validatePodAffinityTerm tests that the specified podAffinityTerm fields have valid data
|
||||||
func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, fldPath *field.Path) field.ErrorList {
|
func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, fldPath.Child("labelSelector"))...)
|
allErrs = append(allErrs, ValidatePodAffinityTermSelector(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath)...)
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.NamespaceSelector, fldPath.Child("namespaceSelector"))...)
|
|
||||||
|
|
||||||
for _, name := range podAffinityTerm.Namespaces {
|
for _, name := range podAffinityTerm.Namespaces {
|
||||||
for _, msg := range ValidateNamespaceName(name, false) {
|
for _, msg := range ValidateNamespaceName(name, false) {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("namespace"), name, msg))
|
||||||
@ -4028,28 +4049,28 @@ func validatePodAffinityTerm(podAffinityTerm core.PodAffinityTerm, fldPath *fiel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data
|
// validatePodAffinityTerms tests that the specified podAffinityTerms fields have valid data
|
||||||
func validatePodAffinityTerms(podAffinityTerms []core.PodAffinityTerm, fldPath *field.Path) field.ErrorList {
|
func validatePodAffinityTerms(podAffinityTerms []core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
for i, podAffinityTerm := range podAffinityTerms {
|
for i, podAffinityTerm := range podAffinityTerms {
|
||||||
allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, fldPath.Index(i))...)
|
allErrs = append(allErrs, validatePodAffinityTerm(podAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(i))...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data
|
// validateWeightedPodAffinityTerms tests that the specified weightedPodAffinityTerms fields have valid data
|
||||||
func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []core.WeightedPodAffinityTerm, fldPath *field.Path) field.ErrorList {
|
func validateWeightedPodAffinityTerms(weightedPodAffinityTerms []core.WeightedPodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
for j, weightedTerm := range weightedPodAffinityTerms {
|
for j, weightedTerm := range weightedPodAffinityTerms {
|
||||||
if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 {
|
if weightedTerm.Weight <= 0 || weightedTerm.Weight > 100 {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Index(j).Child("weight"), weightedTerm.Weight, "must be in the range 1-100"))
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, fldPath.Index(j).Child("podAffinityTerm"))...)
|
allErrs = append(allErrs, validatePodAffinityTerm(weightedTerm.PodAffinityTerm, allowInvalidLabelValueInSelector, fldPath.Index(j).Child("podAffinityTerm"))...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data
|
// validatePodAntiAffinity tests that the specified podAntiAffinity fields have valid data
|
||||||
func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, fldPath *field.Path) field.ErrorList {
|
func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
// TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
// TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||||
// if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
// if podAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||||
@ -4057,11 +4078,11 @@ func validatePodAntiAffinity(podAntiAffinity *core.PodAntiAffinity, fldPath *fie
|
|||||||
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||||
// }
|
// }
|
||||||
if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
if podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||||
allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
|
allErrs = append(allErrs, validatePodAffinityTerms(podAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
|
||||||
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||||
}
|
}
|
||||||
if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
if podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
||||||
allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
|
allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAntiAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
|
||||||
fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
@ -4084,7 +4105,7 @@ func validateNodeAffinity(na *core.NodeAffinity, fldPath *field.Path) field.Erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validatePodAffinity tests that the specified podAffinity fields have valid data
|
// validatePodAffinity tests that the specified podAffinity fields have valid data
|
||||||
func validatePodAffinity(podAffinity *core.PodAffinity, fldPath *field.Path) field.ErrorList {
|
func validatePodAffinity(podAffinity *core.PodAffinity, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
// TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
// TODO:Uncomment below code once RequiredDuringSchedulingRequiredDuringExecution is implemented.
|
||||||
// if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
// if podAffinity.RequiredDuringSchedulingRequiredDuringExecution != nil {
|
||||||
@ -4092,11 +4113,11 @@ func validatePodAffinity(podAffinity *core.PodAffinity, fldPath *field.Path) fie
|
|||||||
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
// fldPath.Child("requiredDuringSchedulingRequiredDuringExecution"))...)
|
||||||
// }
|
// }
|
||||||
if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
if podAffinity.RequiredDuringSchedulingIgnoredDuringExecution != nil {
|
||||||
allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution,
|
allErrs = append(allErrs, validatePodAffinityTerms(podAffinity.RequiredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
|
||||||
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
fldPath.Child("requiredDuringSchedulingIgnoredDuringExecution"))...)
|
||||||
}
|
}
|
||||||
if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
if podAffinity.PreferredDuringSchedulingIgnoredDuringExecution != nil {
|
||||||
allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution,
|
allErrs = append(allErrs, validateWeightedPodAffinityTerms(podAffinity.PreferredDuringSchedulingIgnoredDuringExecution, allowInvalidLabelValueInSelector,
|
||||||
fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
fldPath.Child("preferredDuringSchedulingIgnoredDuringExecution"))...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
@ -7022,3 +7043,11 @@ func sameLoadBalancerClass(oldService, service *core.Service) bool {
|
|||||||
}
|
}
|
||||||
return *oldService.Spec.LoadBalancerClass == *service.Spec.LoadBalancerClass
|
return *oldService.Spec.LoadBalancerClass == *service.Spec.LoadBalancerClass
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ValidatePodAffinityTermSelector(podAffinityTerm core.PodAffinityTerm, allowInvalidLabelValueInSelector bool, fldPath *field.Path) field.ErrorList {
|
||||||
|
var allErrs field.ErrorList
|
||||||
|
labelSelectorValidationOptions := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: allowInvalidLabelValueInSelector}
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.LabelSelector, labelSelectorValidationOptions, fldPath.Child("labelSelector"))...)
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(podAffinityTerm.NamespaceSelector, labelSelectorValidationOptions, fldPath.Child("namespaceSelector"))...)
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
@ -55,6 +55,10 @@ var (
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type NetworkPolicyValidationOptions struct {
|
||||||
|
AllowInvalidLabelValueInSelector bool
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateNetworkPolicyName can be used to check whether the given networkpolicy
|
// ValidateNetworkPolicyName can be used to check whether the given networkpolicy
|
||||||
// name is valid.
|
// name is valid.
|
||||||
func ValidateNetworkPolicyName(name string, prefix bool) []string {
|
func ValidateNetworkPolicyName(name string, prefix bool) []string {
|
||||||
@ -98,17 +102,20 @@ func ValidateNetworkPolicyPort(port *networking.NetworkPolicyPort, portPath *fie
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateNetworkPolicyPeer validates a NetworkPolicyPeer
|
// ValidateNetworkPolicyPeer validates a NetworkPolicyPeer
|
||||||
func ValidateNetworkPolicyPeer(peer *networking.NetworkPolicyPeer, peerPath *field.Path) field.ErrorList {
|
func ValidateNetworkPolicyPeer(peer *networking.NetworkPolicyPeer, opts NetworkPolicyValidationOptions, peerPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
numPeers := 0
|
numPeers := 0
|
||||||
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
|
||||||
if peer.PodSelector != nil {
|
if peer.PodSelector != nil {
|
||||||
numPeers++
|
numPeers++
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.PodSelector, peerPath.Child("podSelector"))...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.PodSelector, labelSelectorValidationOpts, peerPath.Child("podSelector"))...)
|
||||||
}
|
}
|
||||||
if peer.NamespaceSelector != nil {
|
if peer.NamespaceSelector != nil {
|
||||||
numPeers++
|
numPeers++
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.NamespaceSelector, peerPath.Child("namespaceSelector"))...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(peer.NamespaceSelector, labelSelectorValidationOpts, peerPath.Child("namespaceSelector"))...)
|
||||||
}
|
}
|
||||||
if peer.IPBlock != nil {
|
if peer.IPBlock != nil {
|
||||||
numPeers++
|
numPeers++
|
||||||
@ -125,9 +132,16 @@ func ValidateNetworkPolicyPeer(peer *networking.NetworkPolicyPeer, peerPath *fie
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateNetworkPolicySpec tests if required fields in the networkpolicy spec are set.
|
// ValidateNetworkPolicySpec tests if required fields in the networkpolicy spec are set.
|
||||||
func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, fldPath *field.Path) field.ErrorList {
|
func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, opts NetworkPolicyValidationOptions, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&spec.PodSelector, fldPath.Child("podSelector"))...)
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(
|
||||||
|
&spec.PodSelector,
|
||||||
|
labelSelectorValidationOpts,
|
||||||
|
fldPath.Child("podSelector"),
|
||||||
|
)...)
|
||||||
|
|
||||||
// Validate ingress rules.
|
// Validate ingress rules.
|
||||||
for i, ingress := range spec.Ingress {
|
for i, ingress := range spec.Ingress {
|
||||||
@ -138,7 +152,7 @@ func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, fldPath *fiel
|
|||||||
}
|
}
|
||||||
for i, from := range ingress.From {
|
for i, from := range ingress.From {
|
||||||
fromPath := ingressPath.Child("from").Index(i)
|
fromPath := ingressPath.Child("from").Index(i)
|
||||||
allErrs = append(allErrs, ValidateNetworkPolicyPeer(&from, fromPath)...)
|
allErrs = append(allErrs, ValidateNetworkPolicyPeer(&from, opts, fromPath)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Validate egress rules
|
// Validate egress rules
|
||||||
@ -150,7 +164,7 @@ func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, fldPath *fiel
|
|||||||
}
|
}
|
||||||
for i, to := range egress.To {
|
for i, to := range egress.To {
|
||||||
toPath := egressPath.Child("to").Index(i)
|
toPath := egressPath.Child("to").Index(i)
|
||||||
allErrs = append(allErrs, ValidateNetworkPolicyPeer(&to, toPath)...)
|
allErrs = append(allErrs, ValidateNetworkPolicyPeer(&to, opts, toPath)...)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Validate PolicyTypes
|
// Validate PolicyTypes
|
||||||
@ -169,17 +183,33 @@ func ValidateNetworkPolicySpec(spec *networking.NetworkPolicySpec, fldPath *fiel
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ValidateNetworkPolicy validates a networkpolicy.
|
// ValidateNetworkPolicy validates a networkpolicy.
|
||||||
func ValidateNetworkPolicy(np *networking.NetworkPolicy) field.ErrorList {
|
func ValidateNetworkPolicy(np *networking.NetworkPolicy, opts NetworkPolicyValidationOptions) field.ErrorList {
|
||||||
allErrs := apivalidation.ValidateObjectMeta(&np.ObjectMeta, true, ValidateNetworkPolicyName, field.NewPath("metadata"))
|
allErrs := apivalidation.ValidateObjectMeta(&np.ObjectMeta, true, ValidateNetworkPolicyName, field.NewPath("metadata"))
|
||||||
allErrs = append(allErrs, ValidateNetworkPolicySpec(&np.Spec, field.NewPath("spec"))...)
|
allErrs = append(allErrs, ValidateNetworkPolicySpec(&np.Spec, opts, field.NewPath("spec"))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ValidationOptionsForNetworking generates NetworkPolicyValidationOptions for Networking
|
||||||
|
func ValidationOptionsForNetworking(new, old *networking.NetworkPolicy) NetworkPolicyValidationOptions {
|
||||||
|
opts := NetworkPolicyValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
|
}
|
||||||
|
if old != nil {
|
||||||
|
labelSelectorValidationOpts := unversionedvalidation.LabelSelectorValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector,
|
||||||
|
}
|
||||||
|
if len(unversionedvalidation.ValidateLabelSelector(&old.Spec.PodSelector, labelSelectorValidationOpts, nil)) > 0 {
|
||||||
|
opts.AllowInvalidLabelValueInSelector = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateNetworkPolicyUpdate tests if an update to a NetworkPolicy is valid.
|
// ValidateNetworkPolicyUpdate tests if an update to a NetworkPolicy is valid.
|
||||||
func ValidateNetworkPolicyUpdate(update, old *networking.NetworkPolicy) field.ErrorList {
|
func ValidateNetworkPolicyUpdate(update, old *networking.NetworkPolicy, opts NetworkPolicyValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, field.NewPath("metadata"))...)
|
||||||
allErrs = append(allErrs, ValidateNetworkPolicySpec(&update.Spec, field.NewPath("spec"))...)
|
allErrs = append(allErrs, ValidateNetworkPolicySpec(&update.Spec, opts, field.NewPath("spec"))...)
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ func TestValidateNetworkPolicy(t *testing.T) {
|
|||||||
// Success cases are expected to pass validation.
|
// Success cases are expected to pass validation.
|
||||||
|
|
||||||
for k, v := range successCases {
|
for k, v := range successCases {
|
||||||
if errs := ValidateNetworkPolicy(v); len(errs) != 0 {
|
if errs := ValidateNetworkPolicy(v, NetworkPolicyValidationOptions{AllowInvalidLabelValueInSelector: true}); len(errs) != 0 {
|
||||||
t.Errorf("Expected success for the success validation test number %d, got %v", k, errs)
|
t.Errorf("Expected success for the success validation test number %d, got %v", k, errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,7 +368,7 @@ func TestValidateNetworkPolicy(t *testing.T) {
|
|||||||
|
|
||||||
// Error cases are not expected to pass validation.
|
// Error cases are not expected to pass validation.
|
||||||
for testName, networkPolicy := range errorCases {
|
for testName, networkPolicy := range errorCases {
|
||||||
if errs := ValidateNetworkPolicy(networkPolicy); len(errs) == 0 {
|
if errs := ValidateNetworkPolicy(networkPolicy, NetworkPolicyValidationOptions{AllowInvalidLabelValueInSelector: true}); len(errs) == 0 {
|
||||||
t.Errorf("Expected failure for test: %s", testName)
|
t.Errorf("Expected failure for test: %s", testName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -423,7 +423,7 @@ func TestValidateNetworkPolicyUpdate(t *testing.T) {
|
|||||||
for testName, successCase := range successCases {
|
for testName, successCase := range successCases {
|
||||||
successCase.old.ObjectMeta.ResourceVersion = "1"
|
successCase.old.ObjectMeta.ResourceVersion = "1"
|
||||||
successCase.update.ObjectMeta.ResourceVersion = "1"
|
successCase.update.ObjectMeta.ResourceVersion = "1"
|
||||||
if errs := ValidateNetworkPolicyUpdate(&successCase.update, &successCase.old); len(errs) != 0 {
|
if errs := ValidateNetworkPolicyUpdate(&successCase.update, &successCase.old, NetworkPolicyValidationOptions{false}); len(errs) != 0 {
|
||||||
t.Errorf("expected success (%s): %v", testName, errs)
|
t.Errorf("expected success (%s): %v", testName, errs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -450,7 +450,7 @@ func TestValidateNetworkPolicyUpdate(t *testing.T) {
|
|||||||
for testName, errorCase := range errorCases {
|
for testName, errorCase := range errorCases {
|
||||||
errorCase.old.ObjectMeta.ResourceVersion = "1"
|
errorCase.old.ObjectMeta.ResourceVersion = "1"
|
||||||
errorCase.update.ObjectMeta.ResourceVersion = "1"
|
errorCase.update.ObjectMeta.ResourceVersion = "1"
|
||||||
if errs := ValidateNetworkPolicyUpdate(&errorCase.update, &errorCase.old); len(errs) == 0 {
|
if errs := ValidateNetworkPolicyUpdate(&errorCase.update, &errorCase.old, NetworkPolicyValidationOptions{false}); len(errs) == 0 {
|
||||||
t.Errorf("expected failure: %s", testName)
|
t.Errorf("expected failure: %s", testName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
|
appsvalidation "k8s.io/kubernetes/pkg/apis/apps/validation"
|
||||||
core "k8s.io/kubernetes/pkg/apis/core"
|
"k8s.io/kubernetes/pkg/apis/core"
|
||||||
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
apivalidation "k8s.io/kubernetes/pkg/apis/core/validation"
|
||||||
"k8s.io/kubernetes/pkg/apis/policy"
|
"k8s.io/kubernetes/pkg/apis/policy"
|
||||||
)
|
)
|
||||||
@ -44,16 +44,20 @@ const (
|
|||||||
seccompAllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames"
|
seccompAllowedProfilesAnnotationKey = "seccomp.security.alpha.kubernetes.io/allowedProfileNames"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type PodDisruptionBudgetValidationOptions struct {
|
||||||
|
AllowInvalidLabelValueInSelector bool
|
||||||
|
}
|
||||||
|
|
||||||
// ValidatePodDisruptionBudget validates a PodDisruptionBudget and returns an ErrorList
|
// ValidatePodDisruptionBudget validates a PodDisruptionBudget and returns an ErrorList
|
||||||
// with any errors.
|
// with any errors.
|
||||||
func ValidatePodDisruptionBudget(pdb *policy.PodDisruptionBudget) field.ErrorList {
|
func ValidatePodDisruptionBudget(pdb *policy.PodDisruptionBudget, opts PodDisruptionBudgetValidationOptions) field.ErrorList {
|
||||||
allErrs := ValidatePodDisruptionBudgetSpec(pdb.Spec, field.NewPath("spec"))
|
allErrs := ValidatePodDisruptionBudgetSpec(pdb.Spec, opts, field.NewPath("spec"))
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatePodDisruptionBudgetSpec validates a PodDisruptionBudgetSpec and returns an ErrorList
|
// ValidatePodDisruptionBudgetSpec validates a PodDisruptionBudgetSpec and returns an ErrorList
|
||||||
// with any errors.
|
// with any errors.
|
||||||
func ValidatePodDisruptionBudgetSpec(spec policy.PodDisruptionBudgetSpec, fldPath *field.Path) field.ErrorList {
|
func ValidatePodDisruptionBudgetSpec(spec policy.PodDisruptionBudgetSpec, opts PodDisruptionBudgetValidationOptions, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
if spec.MinAvailable != nil && spec.MaxUnavailable != nil {
|
if spec.MinAvailable != nil && spec.MaxUnavailable != nil {
|
||||||
@ -70,7 +74,9 @@ func ValidatePodDisruptionBudgetSpec(spec policy.PodDisruptionBudgetSpec, fldPat
|
|||||||
allErrs = append(allErrs, appsvalidation.IsNotMoreThan100Percent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
|
allErrs = append(allErrs, appsvalidation.IsNotMoreThan100Percent(*spec.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
|
||||||
}
|
}
|
||||||
|
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, fldPath.Child("selector"))...)
|
labelSelectorValidationOptions := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector}
|
||||||
|
|
||||||
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(spec.Selector, labelSelectorValidationOptions, fldPath.Child("selector"))...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ func TestValidatePodDisruptionBudgetSpec(t *testing.T) {
|
|||||||
MinAvailable: &minAvailable,
|
MinAvailable: &minAvailable,
|
||||||
MaxUnavailable: &maxUnavailable,
|
MaxUnavailable: &maxUnavailable,
|
||||||
}
|
}
|
||||||
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
|
errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo"))
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("unexpected success for %v", spec)
|
t.Errorf("unexpected success for %v", spec)
|
||||||
}
|
}
|
||||||
@ -60,7 +60,7 @@ func TestValidateMinAvailablePodDisruptionBudgetSpec(t *testing.T) {
|
|||||||
spec := policy.PodDisruptionBudgetSpec{
|
spec := policy.PodDisruptionBudgetSpec{
|
||||||
MinAvailable: &c,
|
MinAvailable: &c,
|
||||||
}
|
}
|
||||||
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
|
errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo"))
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
t.Errorf("unexpected failure %v for %v", errs, spec)
|
t.Errorf("unexpected failure %v for %v", errs, spec)
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ func TestValidateMinAvailablePodDisruptionBudgetSpec(t *testing.T) {
|
|||||||
spec := policy.PodDisruptionBudgetSpec{
|
spec := policy.PodDisruptionBudgetSpec{
|
||||||
MinAvailable: &c,
|
MinAvailable: &c,
|
||||||
}
|
}
|
||||||
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
|
errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo"))
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("unexpected success for %v", spec)
|
t.Errorf("unexpected success for %v", spec)
|
||||||
}
|
}
|
||||||
@ -92,7 +92,7 @@ func TestValidateMinAvailablePodAndMaxUnavailableDisruptionBudgetSpec(t *testing
|
|||||||
MinAvailable: &c1,
|
MinAvailable: &c1,
|
||||||
MaxUnavailable: &c2,
|
MaxUnavailable: &c2,
|
||||||
}
|
}
|
||||||
errs := ValidatePodDisruptionBudgetSpec(spec, field.NewPath("foo"))
|
errs := ValidatePodDisruptionBudgetSpec(spec, PodDisruptionBudgetValidationOptions{true}, field.NewPath("foo"))
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
t.Errorf("unexpected success for %v", spec)
|
t.Errorf("unexpected success for %v", spec)
|
||||||
}
|
}
|
||||||
|
@ -55,7 +55,11 @@ func ValidateRoleUpdate(role *rbac.Role, oldRole *rbac.Role) field.ErrorList {
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateClusterRole(role *rbac.ClusterRole) field.ErrorList {
|
type ClusterRoleValidationOptions struct {
|
||||||
|
AllowInvalidLabelValueInSelector bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func ValidateClusterRole(role *rbac.ClusterRole, opts ClusterRoleValidationOptions) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, validation.ValidateObjectMeta(&role.ObjectMeta, false, ValidateRBACName, field.NewPath("metadata"))...)
|
||||||
|
|
||||||
@ -65,13 +69,15 @@ func ValidateClusterRole(role *rbac.ClusterRole) field.ErrorList {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
labelSelectorValidationOptions := unversionedvalidation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector}
|
||||||
|
|
||||||
if role.AggregationRule != nil {
|
if role.AggregationRule != nil {
|
||||||
if len(role.AggregationRule.ClusterRoleSelectors) == 0 {
|
if len(role.AggregationRule.ClusterRoleSelectors) == 0 {
|
||||||
allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil"))
|
allErrs = append(allErrs, field.Required(field.NewPath("aggregationRule", "clusterRoleSelectors"), "at least one clusterRoleSelector required if aggregationRule is non-nil"))
|
||||||
}
|
}
|
||||||
for i, selector := range role.AggregationRule.ClusterRoleSelectors {
|
for i, selector := range role.AggregationRule.ClusterRoleSelectors {
|
||||||
fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i)
|
fieldPath := field.NewPath("aggregationRule", "clusterRoleSelectors").Index(i)
|
||||||
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, fieldPath)...)
|
allErrs = append(allErrs, unversionedvalidation.ValidateLabelSelector(&selector, labelSelectorValidationOptions, fieldPath)...)
|
||||||
|
|
||||||
selector, err := metav1.LabelSelectorAsSelector(&selector)
|
selector, err := metav1.LabelSelectorAsSelector(&selector)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -86,8 +92,8 @@ func ValidateClusterRole(role *rbac.ClusterRole) field.ErrorList {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole) field.ErrorList {
|
func ValidateClusterRoleUpdate(role *rbac.ClusterRole, oldRole *rbac.ClusterRole, opts ClusterRoleValidationOptions) field.ErrorList {
|
||||||
allErrs := ValidateClusterRole(role)
|
allErrs := ValidateClusterRole(role, opts)
|
||||||
allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...)
|
allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(&role.ObjectMeta, &oldRole.ObjectMeta, field.NewPath("metadata"))...)
|
||||||
|
|
||||||
return allErrs
|
return allErrs
|
||||||
|
@ -330,7 +330,7 @@ type ValidateClusterRoleTest struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v ValidateClusterRoleTest) test(t *testing.T) {
|
func (v ValidateClusterRoleTest) test(t *testing.T) {
|
||||||
errs := ValidateClusterRole(&v.role)
|
errs := ValidateClusterRole(&v.role, ClusterRoleValidationOptions{false})
|
||||||
if len(errs) == 0 {
|
if len(errs) == 0 {
|
||||||
if v.wantErr {
|
if v.wantErr {
|
||||||
t.Fatal("expected validation error")
|
t.Fatal("expected validation error")
|
||||||
|
@ -537,10 +537,15 @@ func validateVolumeLifecycleModes(modes []storage.VolumeLifecycleMode, fldPath *
|
|||||||
// CSIStorageCapacity object.
|
// CSIStorageCapacity object.
|
||||||
var ValidateStorageCapacityName = apimachineryvalidation.NameIsDNSSubdomain
|
var ValidateStorageCapacityName = apimachineryvalidation.NameIsDNSSubdomain
|
||||||
|
|
||||||
|
type CSIStorageCapacityValidateOptions struct {
|
||||||
|
AllowInvalidLabelValueInSelector bool
|
||||||
|
}
|
||||||
|
|
||||||
// ValidateCSIStorageCapacity validates a CSIStorageCapacity.
|
// ValidateCSIStorageCapacity validates a CSIStorageCapacity.
|
||||||
func ValidateCSIStorageCapacity(capacity *storage.CSIStorageCapacity) field.ErrorList {
|
func ValidateCSIStorageCapacity(capacity *storage.CSIStorageCapacity, opts CSIStorageCapacityValidateOptions) field.ErrorList {
|
||||||
allErrs := apivalidation.ValidateObjectMeta(&capacity.ObjectMeta, true, ValidateStorageCapacityName, field.NewPath("metadata"))
|
allErrs := apivalidation.ValidateObjectMeta(&capacity.ObjectMeta, true, ValidateStorageCapacityName, field.NewPath("metadata"))
|
||||||
allErrs = append(allErrs, metav1validation.ValidateLabelSelector(capacity.NodeTopology, field.NewPath("nodeTopology"))...)
|
labelSelectorValidationOptions := metav1validation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: opts.AllowInvalidLabelValueInSelector}
|
||||||
|
allErrs = append(allErrs, metav1validation.ValidateLabelSelector(capacity.NodeTopology, labelSelectorValidationOptions, field.NewPath("nodeTopology"))...)
|
||||||
for _, msg := range apivalidation.ValidateClassName(capacity.StorageClassName, false) {
|
for _, msg := range apivalidation.ValidateClassName(capacity.StorageClassName, false) {
|
||||||
allErrs = append(allErrs, field.Invalid(field.NewPath("storageClassName"), capacity.StorageClassName, msg))
|
allErrs = append(allErrs, field.Invalid(field.NewPath("storageClassName"), capacity.StorageClassName, msg))
|
||||||
}
|
}
|
||||||
|
@ -2179,7 +2179,7 @@ func TestValidateCSIStorageCapacity(t *testing.T) {
|
|||||||
|
|
||||||
for name, scenario := range scenarios {
|
for name, scenario := range scenarios {
|
||||||
t.Run(name, func(t *testing.T) {
|
t.Run(name, func(t *testing.T) {
|
||||||
errs := ValidateCSIStorageCapacity(scenario.capacity)
|
errs := ValidateCSIStorageCapacity(scenario.capacity, CSIStorageCapacityValidateOptions{false})
|
||||||
if len(errs) == 0 && scenario.isExpectedFailure {
|
if len(errs) == 0 && scenario.isExpectedFailure {
|
||||||
t.Errorf("Unexpected success")
|
t.Errorf("Unexpected success")
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import (
|
|||||||
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
extensionsv1beta1 "k8s.io/api/extensions/v1beta1"
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
apivalidation "k8s.io/apimachinery/pkg/api/validation"
|
apivalidation "k8s.io/apimachinery/pkg/api/validation"
|
||||||
|
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
@ -145,6 +146,8 @@ func (daemonSetStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Ob
|
|||||||
oldDaemonSet := old.(*apps.DaemonSet)
|
oldDaemonSet := old.(*apps.DaemonSet)
|
||||||
|
|
||||||
opts := pod.GetValidationOptionsFromPodTemplate(&newDaemonSet.Spec.Template, &oldDaemonSet.Spec.Template)
|
opts := pod.GetValidationOptionsFromPodTemplate(&newDaemonSet.Spec.Template, &oldDaemonSet.Spec.Template)
|
||||||
|
opts.AllowInvalidLabelValueInSelector = opts.AllowInvalidLabelValueInSelector || metav1validation.LabelSelectorHasInvalidLabelValue(oldDaemonSet.Spec.Selector)
|
||||||
|
|
||||||
allErrs := validation.ValidateDaemonSet(obj.(*apps.DaemonSet), opts)
|
allErrs := validation.ValidateDaemonSet(obj.(*apps.DaemonSet), opts)
|
||||||
allErrs = append(allErrs, validation.ValidateDaemonSetUpdate(newDaemonSet, oldDaemonSet, opts)...)
|
allErrs = append(allErrs, validation.ValidateDaemonSetUpdate(newDaemonSet, oldDaemonSet, opts)...)
|
||||||
|
|
||||||
|
@ -105,6 +105,19 @@ func TestSelectorImmutability(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateToleratingBadLabels(t *testing.T) {
|
||||||
|
oldObj := newDaemonSetWithSelectorLabels(map[string]string{"a": "b"}, 1)
|
||||||
|
oldObj.Spec.Selector.MatchExpressions = []metav1.LabelSelectorRequirement{{Key: "key", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"bad value"}}}
|
||||||
|
newObj := newDaemonSetWithSelectorLabels(map[string]string{"a": "b"}, 1)
|
||||||
|
newObj.Spec.Selector.MatchExpressions = []metav1.LabelSelectorRequirement{{Key: "key", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"bad value"}}}
|
||||||
|
|
||||||
|
context := genericapirequest.NewContext()
|
||||||
|
errorList := daemonSetStrategy{}.ValidateUpdate(context, newObj, oldObj)
|
||||||
|
if len(errorList) > 0 {
|
||||||
|
t.Errorf("Unexpected error list with no-op update of bad object: %v", errorList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newDaemonSetWithSelectorLabels(selectorLabels map[string]string, templateGeneration int64) *apps.DaemonSet {
|
func newDaemonSetWithSelectorLabels(selectorLabels map[string]string, templateGeneration int64) *apps.DaemonSet {
|
||||||
return &apps.DaemonSet{
|
return &apps.DaemonSet{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
@ -24,6 +24,7 @@ import (
|
|||||||
batchv1 "k8s.io/api/batch/v1"
|
batchv1 "k8s.io/api/batch/v1"
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
"k8s.io/apimachinery/pkg/fields"
|
"k8s.io/apimachinery/pkg/fields"
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
@ -169,6 +170,8 @@ func validationOptionsForJob(newJob, oldJob *batch.Job) validation.JobValidation
|
|||||||
AllowTrackingAnnotation: true,
|
AllowTrackingAnnotation: true,
|
||||||
}
|
}
|
||||||
if oldJob != nil {
|
if oldJob != nil {
|
||||||
|
opts.AllowInvalidLabelValueInSelector = opts.AllowInvalidLabelValueInSelector || metav1validation.LabelSelectorHasInvalidLabelValue(oldJob.Spec.Selector)
|
||||||
|
|
||||||
// Because we don't support the tracking with finalizers for already
|
// Because we don't support the tracking with finalizers for already
|
||||||
// existing jobs, we allow the annotation only if the Job already had it,
|
// existing jobs, we allow the annotation only if the Job already had it,
|
||||||
// regardless of the feature gate.
|
// regardless of the feature gate.
|
||||||
|
@ -449,6 +449,31 @@ func TestJobStrategy(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidateToleratingBadLabels(t *testing.T) {
|
||||||
|
invalidSelector := getValidLabelSelector()
|
||||||
|
invalidSelector.MatchExpressions = []metav1.LabelSelectorRequirement{{Key: "key", Operator: metav1.LabelSelectorOpNotIn, Values: []string{"bad value"}}}
|
||||||
|
|
||||||
|
validPodTemplateSpec := getValidPodTemplateSpecForSelector(getValidLabelSelector())
|
||||||
|
job := &batch.Job{
|
||||||
|
ObjectMeta: getValidObjectMeta(0),
|
||||||
|
Spec: batch.JobSpec{
|
||||||
|
Selector: invalidSelector,
|
||||||
|
ManualSelector: pointer.BoolPtr(true),
|
||||||
|
Template: validPodTemplateSpec,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
job.ResourceVersion = "1"
|
||||||
|
|
||||||
|
oldObj := job.DeepCopy()
|
||||||
|
newObj := job.DeepCopy()
|
||||||
|
|
||||||
|
context := genericapirequest.NewContext()
|
||||||
|
errorList := Strategy.ValidateUpdate(context, newObj, oldObj)
|
||||||
|
if len(errorList) > 0 {
|
||||||
|
t.Errorf("Unexpected error list with no-op update of bad object: %v", errorList)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestJobStrategyValidateUpdate(t *testing.T) {
|
func TestJobStrategyValidateUpdate(t *testing.T) {
|
||||||
ctx := genericapirequest.NewDefaultContext()
|
ctx := genericapirequest.NewDefaultContext()
|
||||||
validSelector := &metav1.LabelSelector{
|
validSelector := &metav1.LabelSelector{
|
||||||
|
@ -96,7 +96,8 @@ func (networkPolicyStrategy) PrepareForUpdate(ctx context.Context, obj, old runt
|
|||||||
// Validate validates a new NetworkPolicy.
|
// Validate validates a new NetworkPolicy.
|
||||||
func (networkPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (networkPolicyStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
networkPolicy := obj.(*networking.NetworkPolicy)
|
networkPolicy := obj.(*networking.NetworkPolicy)
|
||||||
return validation.ValidateNetworkPolicy(networkPolicy)
|
ops := validation.ValidationOptionsForNetworking(networkPolicy, nil)
|
||||||
|
return validation.ValidateNetworkPolicy(networkPolicy, ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||||
@ -114,8 +115,9 @@ func (networkPolicyStrategy) AllowCreateOnUpdate() bool {
|
|||||||
|
|
||||||
// ValidateUpdate is the default update validation for an end user.
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
func (networkPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (networkPolicyStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
validationErrorList := validation.ValidateNetworkPolicy(obj.(*networking.NetworkPolicy))
|
opts := validation.ValidationOptionsForNetworking(obj.(*networking.NetworkPolicy), old.(*networking.NetworkPolicy))
|
||||||
updateErrorList := validation.ValidateNetworkPolicyUpdate(obj.(*networking.NetworkPolicy), old.(*networking.NetworkPolicy))
|
validationErrorList := validation.ValidateNetworkPolicy(obj.(*networking.NetworkPolicy), opts)
|
||||||
|
updateErrorList := validation.ValidateNetworkPolicyUpdate(obj.(*networking.NetworkPolicy), old.(*networking.NetworkPolicy), opts)
|
||||||
return append(validationErrorList, updateErrorList...)
|
return append(validationErrorList, updateErrorList...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
@ -87,7 +88,10 @@ func (podDisruptionBudgetStrategy) PrepareForUpdate(ctx context.Context, obj, ol
|
|||||||
// Validate validates a new PodDisruptionBudget.
|
// Validate validates a new PodDisruptionBudget.
|
||||||
func (podDisruptionBudgetStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (podDisruptionBudgetStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
podDisruptionBudget := obj.(*policy.PodDisruptionBudget)
|
podDisruptionBudget := obj.(*policy.PodDisruptionBudget)
|
||||||
return validation.ValidatePodDisruptionBudget(podDisruptionBudget)
|
opts := validation.PodDisruptionBudgetValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
|
}
|
||||||
|
return validation.ValidatePodDisruptionBudget(podDisruptionBudget, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||||
@ -106,7 +110,10 @@ func (podDisruptionBudgetStrategy) AllowCreateOnUpdate() bool {
|
|||||||
|
|
||||||
// ValidateUpdate is the default update validation for an end user.
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
func (podDisruptionBudgetStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (podDisruptionBudgetStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
return validation.ValidatePodDisruptionBudget(obj.(*policy.PodDisruptionBudget))
|
opts := validation.PodDisruptionBudgetValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: hasInvalidLabelValueInLabelSelector(old.(*policy.PodDisruptionBudget)),
|
||||||
|
}
|
||||||
|
return validation.ValidatePodDisruptionBudget(obj.(*policy.PodDisruptionBudget), opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnUpdate returns warnings for the given update.
|
// WarningsOnUpdate returns warnings for the given update.
|
||||||
@ -167,3 +174,11 @@ func (podDisruptionBudgetStatusStrategy) ValidateUpdate(ctx context.Context, obj
|
|||||||
func (podDisruptionBudgetStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
func (podDisruptionBudgetStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasInvalidLabelValueInLabelSelector(pdb *policy.PodDisruptionBudget) bool {
|
||||||
|
if pdb.Spec.Selector != nil {
|
||||||
|
labelSelectorValidationOptions := metav1validation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}
|
||||||
|
return len(metav1validation.ValidateLabelSelector(pdb.Spec.Selector, labelSelectorValidationOptions, nil)) > 0
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ package clusterrole
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
@ -71,7 +72,10 @@ func (strategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) {
|
|||||||
// Validate validates a new ClusterRole. Validation must check for a correct signature.
|
// Validate validates a new ClusterRole. Validation must check for a correct signature.
|
||||||
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (strategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
clusterRole := obj.(*rbac.ClusterRole)
|
clusterRole := obj.(*rbac.ClusterRole)
|
||||||
return validation.ValidateClusterRole(clusterRole)
|
opts := validation.ClusterRoleValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: false,
|
||||||
|
}
|
||||||
|
return validation.ValidateClusterRole(clusterRole, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||||
@ -85,8 +89,12 @@ func (strategy) Canonicalize(obj runtime.Object) {
|
|||||||
// ValidateUpdate is the default update validation for an end user.
|
// ValidateUpdate is the default update validation for an end user.
|
||||||
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (strategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
newObj := obj.(*rbac.ClusterRole)
|
newObj := obj.(*rbac.ClusterRole)
|
||||||
errorList := validation.ValidateClusterRole(newObj)
|
oldObj := old.(*rbac.ClusterRole)
|
||||||
return append(errorList, validation.ValidateClusterRoleUpdate(newObj, old.(*rbac.ClusterRole))...)
|
opts := validation.ClusterRoleValidationOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: hasInvalidLabelValueInLabelSelector(oldObj),
|
||||||
|
}
|
||||||
|
errorList := validation.ValidateClusterRole(newObj, opts)
|
||||||
|
return append(errorList, validation.ValidateClusterRoleUpdate(newObj, old.(*rbac.ClusterRole), opts)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WarningsOnUpdate returns warnings for the given update.
|
// WarningsOnUpdate returns warnings for the given update.
|
||||||
@ -102,3 +110,15 @@ func (strategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) [
|
|||||||
func (strategy) AllowUnconditionalUpdate() bool {
|
func (strategy) AllowUnconditionalUpdate() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasInvalidLabelValueInLabelSelector(role *rbac.ClusterRole) bool {
|
||||||
|
if role.AggregationRule != nil {
|
||||||
|
labelSelectorValidationOptions := metav1validation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}
|
||||||
|
for _, selector := range role.AggregationRule.ClusterRoleSelectors {
|
||||||
|
if len(metav1validation.ValidateLabelSelector(&selector, labelSelectorValidationOptions, nil)) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -19,6 +19,7 @@ package csistoragecapacity
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
"k8s.io/apiserver/pkg/storage/names"
|
"k8s.io/apiserver/pkg/storage/names"
|
||||||
@ -48,9 +49,11 @@ func (csiStorageCapacityStrategy) PrepareForCreate(ctx context.Context, obj runt
|
|||||||
|
|
||||||
func (csiStorageCapacityStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
func (csiStorageCapacityStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList {
|
||||||
csiStorageCapacity := obj.(*storage.CSIStorageCapacity)
|
csiStorageCapacity := obj.(*storage.CSIStorageCapacity)
|
||||||
|
opts := validation.CSIStorageCapacityValidateOptions{
|
||||||
errs := validation.ValidateCSIStorageCapacity(csiStorageCapacity)
|
AllowInvalidLabelValueInSelector: false,
|
||||||
errs = append(errs, validation.ValidateCSIStorageCapacity(csiStorageCapacity)...)
|
}
|
||||||
|
errs := validation.ValidateCSIStorageCapacity(csiStorageCapacity, opts)
|
||||||
|
errs = append(errs, validation.ValidateCSIStorageCapacity(csiStorageCapacity, opts)...)
|
||||||
|
|
||||||
return errs
|
return errs
|
||||||
}
|
}
|
||||||
@ -75,7 +78,10 @@ func (csiStorageCapacityStrategy) PrepareForUpdate(ctx context.Context, obj, old
|
|||||||
func (csiStorageCapacityStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
func (csiStorageCapacityStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList {
|
||||||
newCSIStorageCapacityObj := obj.(*storage.CSIStorageCapacity)
|
newCSIStorageCapacityObj := obj.(*storage.CSIStorageCapacity)
|
||||||
oldCSIStorageCapacityObj := old.(*storage.CSIStorageCapacity)
|
oldCSIStorageCapacityObj := old.(*storage.CSIStorageCapacity)
|
||||||
errorList := validation.ValidateCSIStorageCapacity(newCSIStorageCapacityObj)
|
opts := validation.CSIStorageCapacityValidateOptions{
|
||||||
|
AllowInvalidLabelValueInSelector: hasInvalidLabelValueInLabelSelector(oldCSIStorageCapacityObj),
|
||||||
|
}
|
||||||
|
errorList := validation.ValidateCSIStorageCapacity(newCSIStorageCapacityObj, opts)
|
||||||
return append(errorList, validation.ValidateCSIStorageCapacityUpdate(newCSIStorageCapacityObj, oldCSIStorageCapacityObj)...)
|
return append(errorList, validation.ValidateCSIStorageCapacityUpdate(newCSIStorageCapacityObj, oldCSIStorageCapacityObj)...)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,3 +93,8 @@ func (csiStorageCapacityStrategy) WarningsOnUpdate(ctx context.Context, obj, old
|
|||||||
func (csiStorageCapacityStrategy) AllowUnconditionalUpdate() bool {
|
func (csiStorageCapacityStrategy) AllowUnconditionalUpdate() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasInvalidLabelValueInLabelSelector(capacity *storage.CSIStorageCapacity) bool {
|
||||||
|
labelSelectorValidationOptions := metav1validation.LabelSelectorValidationOptions{AllowInvalidLabelValueInSelector: false}
|
||||||
|
return len(metav1validation.ValidateLabelSelector(capacity.NodeTopology, labelSelectorValidationOptions, nil)) > 0
|
||||||
|
}
|
||||||
|
@ -28,19 +28,46 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidateLabelSelector(ps *metav1.LabelSelector, fldPath *field.Path) field.ErrorList {
|
// LabelSelectorValidationOptions is a struct that can be passed to ValidateLabelSelector to record the validate options
|
||||||
|
type LabelSelectorValidationOptions struct {
|
||||||
|
// Allow invalid label value in selector
|
||||||
|
AllowInvalidLabelValueInSelector bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// LabelSelectorHasInvalidLabelValue returns true if the given selector contains an invalid label value in a match expression.
|
||||||
|
// This is useful for determining whether AllowInvalidLabelValueInSelector should be set to true when validating an update
|
||||||
|
// based on existing persisted invalid values.
|
||||||
|
func LabelSelectorHasInvalidLabelValue(ps *metav1.LabelSelector) bool {
|
||||||
|
if ps == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, e := range ps.MatchExpressions {
|
||||||
|
for _, v := range e.Values {
|
||||||
|
if len(validation.IsValidLabelValue(v)) > 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLabelSelector validate the LabelSelector according to the opts and returns any validation errors.
|
||||||
|
// opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data.
|
||||||
|
func ValidateLabelSelector(ps *metav1.LabelSelector, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
if ps == nil {
|
if ps == nil {
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...)
|
allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...)
|
||||||
for i, expr := range ps.MatchExpressions {
|
for i, expr := range ps.MatchExpressions {
|
||||||
allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, fldPath.Child("matchExpressions").Index(i))...)
|
allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, opts, fldPath.Child("matchExpressions").Index(i))...)
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, fldPath *field.Path) field.ErrorList {
|
// ValidateLabelSelectorRequirement validate the requirement according to the opts and returns any validation errors.
|
||||||
|
// opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data.
|
||||||
|
func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
switch sr.Operator {
|
switch sr.Operator {
|
||||||
case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn:
|
case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn:
|
||||||
@ -55,6 +82,13 @@ func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, fldPat
|
|||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator"))
|
||||||
}
|
}
|
||||||
allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
|
allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...)
|
||||||
|
if !opts.AllowInvalidLabelValueInSelector {
|
||||||
|
for valueIndex, value := range sr.Values {
|
||||||
|
for _, msg := range validation.IsValidLabelValue(value) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(valueIndex), value, msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -427,6 +427,98 @@ func TestValidateConditions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestLabelSelectorMatchExpression(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
labelSelector *metav1.LabelSelector
|
||||||
|
wantErrorNumber int
|
||||||
|
validateErrs func(t *testing.T, errs field.ErrorList)
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Valid LabelSelector",
|
||||||
|
labelSelector: &metav1.LabelSelector{
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "key",
|
||||||
|
Operator: metav1.LabelSelectorOpIn,
|
||||||
|
Values: []string{"value"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErrorNumber: 0,
|
||||||
|
validateErrs: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MatchExpression's key name isn't valid",
|
||||||
|
labelSelector: &metav1.LabelSelector{
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "-key",
|
||||||
|
Operator: metav1.LabelSelectorOpIn,
|
||||||
|
Values: []string{"value"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErrorNumber: 1,
|
||||||
|
validateErrs: func(t *testing.T, errs field.ErrorList) {
|
||||||
|
errMessage := "name part must consist of alphanumeric characters"
|
||||||
|
if !partStringInErrorMessage(errs, errMessage) {
|
||||||
|
t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MatchExpression's operator isn't valid",
|
||||||
|
labelSelector: &metav1.LabelSelector{
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "key",
|
||||||
|
Operator: "abc",
|
||||||
|
Values: []string{"value"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErrorNumber: 1,
|
||||||
|
validateErrs: func(t *testing.T, errs field.ErrorList) {
|
||||||
|
errMessage := "not a valid selector operator"
|
||||||
|
if !partStringInErrorMessage(errs, errMessage) {
|
||||||
|
t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MatchExpression's value name isn't valid",
|
||||||
|
labelSelector: &metav1.LabelSelector{
|
||||||
|
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||||
|
{
|
||||||
|
Key: "key",
|
||||||
|
Operator: metav1.LabelSelectorOpIn,
|
||||||
|
Values: []string{"-value"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErrorNumber: 1,
|
||||||
|
validateErrs: func(t *testing.T, errs field.ErrorList) {
|
||||||
|
errMessage := "a valid label must be an empty string or consist of"
|
||||||
|
if !partStringInErrorMessage(errs, errMessage) {
|
||||||
|
t.Errorf("missing %q in\n%v", errMessage, errorsAsString(errs))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for index, testCase := range testCases {
|
||||||
|
t.Run(testCase.name, func(t *testing.T) {
|
||||||
|
allErrs := ValidateLabelSelector(testCase.labelSelector, LabelSelectorValidationOptions{false}, field.NewPath("labelSelector"))
|
||||||
|
if len(allErrs) != testCase.wantErrorNumber {
|
||||||
|
t.Errorf("case[%d]: expected failure", index)
|
||||||
|
}
|
||||||
|
if len(allErrs) >= 1 && testCase.validateErrs != nil {
|
||||||
|
testCase.validateErrs(t, allErrs)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func hasError(errs field.ErrorList, needle string) bool {
|
func hasError(errs field.ErrorList, needle string) bool {
|
||||||
for _, curr := range errs {
|
for _, curr := range errs {
|
||||||
if curr.Error() == needle {
|
if curr.Error() == needle {
|
||||||
@ -445,6 +537,15 @@ func hasPrefixError(errs field.ErrorList, prefix string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func partStringInErrorMessage(errs field.ErrorList, prefix string) bool {
|
||||||
|
for _, curr := range errs {
|
||||||
|
if strings.Contains(curr.Error(), prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func errorsAsString(errs field.ErrorList) string {
|
func errorsAsString(errs field.ErrorList) string {
|
||||||
messages := []string{}
|
messages := []string{}
|
||||||
for _, curr := range errs {
|
for _, curr := range errs {
|
||||||
|
Loading…
Reference in New Issue
Block a user