github.com/spotmaxtech/k8s-apimachinery-v0260@v0.0.1/pkg/apis/meta/v1/validation/validation.go (about) 1 /* 2 Copyright 2015 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package validation 18 19 import ( 20 "fmt" 21 "regexp" 22 "unicode" 23 24 metav1 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/apis/meta/v1" 25 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/types" 26 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/sets" 27 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/validation" 28 "github.com/spotmaxtech/k8s-apimachinery-v0260/pkg/util/validation/field" 29 ) 30 31 // LabelSelectorValidationOptions is a struct that can be passed to ValidateLabelSelector to record the validate options 32 type LabelSelectorValidationOptions struct { 33 // Allow invalid label value in selector 34 AllowInvalidLabelValueInSelector bool 35 } 36 37 // LabelSelectorHasInvalidLabelValue returns true if the given selector contains an invalid label value in a match expression. 38 // This is useful for determining whether AllowInvalidLabelValueInSelector should be set to true when validating an update 39 // based on existing persisted invalid values. 40 func LabelSelectorHasInvalidLabelValue(ps *metav1.LabelSelector) bool { 41 if ps == nil { 42 return false 43 } 44 for _, e := range ps.MatchExpressions { 45 for _, v := range e.Values { 46 if len(validation.IsValidLabelValue(v)) > 0 { 47 return true 48 } 49 } 50 } 51 return false 52 } 53 54 // ValidateLabelSelector validate the LabelSelector according to the opts and returns any validation errors. 55 // opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data. 56 func ValidateLabelSelector(ps *metav1.LabelSelector, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList { 57 allErrs := field.ErrorList{} 58 if ps == nil { 59 return allErrs 60 } 61 allErrs = append(allErrs, ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...) 62 for i, expr := range ps.MatchExpressions { 63 allErrs = append(allErrs, ValidateLabelSelectorRequirement(expr, opts, fldPath.Child("matchExpressions").Index(i))...) 64 } 65 return allErrs 66 } 67 68 // ValidateLabelSelectorRequirement validate the requirement according to the opts and returns any validation errors. 69 // opts.AllowInvalidLabelValueInSelector is only expected to be set to true when required for backwards compatibility with existing invalid data. 70 func ValidateLabelSelectorRequirement(sr metav1.LabelSelectorRequirement, opts LabelSelectorValidationOptions, fldPath *field.Path) field.ErrorList { 71 allErrs := field.ErrorList{} 72 switch sr.Operator { 73 case metav1.LabelSelectorOpIn, metav1.LabelSelectorOpNotIn: 74 if len(sr.Values) == 0 { 75 allErrs = append(allErrs, field.Required(fldPath.Child("values"), "must be specified when `operator` is 'In' or 'NotIn'")) 76 } 77 case metav1.LabelSelectorOpExists, metav1.LabelSelectorOpDoesNotExist: 78 if len(sr.Values) > 0 { 79 allErrs = append(allErrs, field.Forbidden(fldPath.Child("values"), "may not be specified when `operator` is 'Exists' or 'DoesNotExist'")) 80 } 81 default: 82 allErrs = append(allErrs, field.Invalid(fldPath.Child("operator"), sr.Operator, "not a valid selector operator")) 83 } 84 allErrs = append(allErrs, ValidateLabelName(sr.Key, fldPath.Child("key"))...) 85 if !opts.AllowInvalidLabelValueInSelector { 86 for valueIndex, value := range sr.Values { 87 for _, msg := range validation.IsValidLabelValue(value) { 88 allErrs = append(allErrs, field.Invalid(fldPath.Child("values").Index(valueIndex), value, msg)) 89 } 90 } 91 } 92 return allErrs 93 } 94 95 // ValidateLabelName validates that the label name is correctly defined. 96 func ValidateLabelName(labelName string, fldPath *field.Path) field.ErrorList { 97 allErrs := field.ErrorList{} 98 for _, msg := range validation.IsQualifiedName(labelName) { 99 allErrs = append(allErrs, field.Invalid(fldPath, labelName, msg)) 100 } 101 return allErrs 102 } 103 104 // ValidateLabels validates that a set of labels are correctly defined. 105 func ValidateLabels(labels map[string]string, fldPath *field.Path) field.ErrorList { 106 allErrs := field.ErrorList{} 107 for k, v := range labels { 108 allErrs = append(allErrs, ValidateLabelName(k, fldPath)...) 109 for _, msg := range validation.IsValidLabelValue(v) { 110 allErrs = append(allErrs, field.Invalid(fldPath, v, msg)) 111 } 112 } 113 return allErrs 114 } 115 116 func ValidateDeleteOptions(options *metav1.DeleteOptions) field.ErrorList { 117 allErrs := field.ErrorList{} 118 //lint:file-ignore SA1019 Keep validation for deprecated OrphanDependents option until it's being removed 119 if options.OrphanDependents != nil && options.PropagationPolicy != nil { 120 allErrs = append(allErrs, field.Invalid(field.NewPath("propagationPolicy"), options.PropagationPolicy, "orphanDependents and deletionPropagation cannot be both set")) 121 } 122 if options.PropagationPolicy != nil && 123 *options.PropagationPolicy != metav1.DeletePropagationForeground && 124 *options.PropagationPolicy != metav1.DeletePropagationBackground && 125 *options.PropagationPolicy != metav1.DeletePropagationOrphan { 126 allErrs = append(allErrs, field.NotSupported(field.NewPath("propagationPolicy"), options.PropagationPolicy, []string{string(metav1.DeletePropagationForeground), string(metav1.DeletePropagationBackground), string(metav1.DeletePropagationOrphan), "nil"})) 127 } 128 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) 129 return allErrs 130 } 131 132 func ValidateCreateOptions(options *metav1.CreateOptions) field.ErrorList { 133 allErrs := field.ErrorList{} 134 allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) 135 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) 136 allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) 137 return allErrs 138 } 139 140 func ValidateUpdateOptions(options *metav1.UpdateOptions) field.ErrorList { 141 allErrs := field.ErrorList{} 142 allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) 143 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) 144 allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) 145 return allErrs 146 } 147 148 func ValidatePatchOptions(options *metav1.PatchOptions, patchType types.PatchType) field.ErrorList { 149 allErrs := field.ErrorList{} 150 if patchType != types.ApplyPatchType { 151 if options.Force != nil { 152 allErrs = append(allErrs, field.Forbidden(field.NewPath("force"), "may not be specified for non-apply patch")) 153 } 154 } else { 155 if options.FieldManager == "" { 156 // This field is defaulted to "kubectl" by kubectl, but HAS TO be explicitly set by controllers. 157 allErrs = append(allErrs, field.Required(field.NewPath("fieldManager"), "is required for apply patch")) 158 } 159 } 160 allErrs = append(allErrs, ValidateFieldManager(options.FieldManager, field.NewPath("fieldManager"))...) 161 allErrs = append(allErrs, ValidateDryRun(field.NewPath("dryRun"), options.DryRun)...) 162 allErrs = append(allErrs, ValidateFieldValidation(field.NewPath("fieldValidation"), options.FieldValidation)...) 163 return allErrs 164 } 165 166 var FieldManagerMaxLength = 128 167 168 // ValidateFieldManager valides that the fieldManager is the proper length and 169 // only has printable characters. 170 func ValidateFieldManager(fieldManager string, fldPath *field.Path) field.ErrorList { 171 allErrs := field.ErrorList{} 172 // the field can not be set as a `*string`, so a empty string ("") is 173 // considered as not set and is defaulted by the rest of the process 174 // (unless apply is used, in which case it is required). 175 if len(fieldManager) > FieldManagerMaxLength { 176 allErrs = append(allErrs, field.TooLong(fldPath, fieldManager, FieldManagerMaxLength)) 177 } 178 // Verify that all characters are printable. 179 for i, r := range fieldManager { 180 if !unicode.IsPrint(r) { 181 allErrs = append(allErrs, field.Invalid(fldPath, fieldManager, fmt.Sprintf("invalid character %#U (at position %d)", r, i))) 182 } 183 } 184 185 return allErrs 186 } 187 188 var allowedDryRunValues = sets.NewString(metav1.DryRunAll) 189 190 // ValidateDryRun validates that a dryRun query param only contains allowed values. 191 func ValidateDryRun(fldPath *field.Path, dryRun []string) field.ErrorList { 192 allErrs := field.ErrorList{} 193 if !allowedDryRunValues.HasAll(dryRun...) { 194 allErrs = append(allErrs, field.NotSupported(fldPath, dryRun, allowedDryRunValues.List())) 195 } 196 return allErrs 197 } 198 199 var allowedFieldValidationValues = sets.NewString("", metav1.FieldValidationIgnore, metav1.FieldValidationWarn, metav1.FieldValidationStrict) 200 201 // ValidateFieldValidation validates that a fieldValidation query param only contains allowed values. 202 func ValidateFieldValidation(fldPath *field.Path, fieldValidation string) field.ErrorList { 203 allErrs := field.ErrorList{} 204 if !allowedFieldValidationValues.Has(fieldValidation) { 205 allErrs = append(allErrs, field.NotSupported(fldPath, fieldValidation, allowedFieldValidationValues.List())) 206 } 207 return allErrs 208 209 } 210 211 const UninitializedStatusUpdateErrorMsg string = `must not update status when the object is uninitialized` 212 213 // ValidateTableOptions returns any invalid flags on TableOptions. 214 func ValidateTableOptions(opts *metav1.TableOptions) field.ErrorList { 215 var allErrs field.ErrorList 216 switch opts.IncludeObject { 217 case metav1.IncludeMetadata, metav1.IncludeNone, metav1.IncludeObject, "": 218 default: 219 allErrs = append(allErrs, field.Invalid(field.NewPath("includeObject"), opts.IncludeObject, "must be 'Metadata', 'Object', 'None', or empty")) 220 } 221 return allErrs 222 } 223 224 const MaxSubresourceNameLength = 256 225 226 func ValidateManagedFields(fieldsList []metav1.ManagedFieldsEntry, fldPath *field.Path) field.ErrorList { 227 var allErrs field.ErrorList 228 for i, fields := range fieldsList { 229 fldPath := fldPath.Index(i) 230 switch fields.Operation { 231 case metav1.ManagedFieldsOperationApply, metav1.ManagedFieldsOperationUpdate: 232 default: 233 allErrs = append(allErrs, field.Invalid(fldPath.Child("operation"), fields.Operation, "must be `Apply` or `Update`")) 234 } 235 if len(fields.FieldsType) > 0 && fields.FieldsType != "FieldsV1" { 236 allErrs = append(allErrs, field.Invalid(fldPath.Child("fieldsType"), fields.FieldsType, "must be `FieldsV1`")) 237 } 238 allErrs = append(allErrs, ValidateFieldManager(fields.Manager, fldPath.Child("manager"))...) 239 240 if len(fields.Subresource) > MaxSubresourceNameLength { 241 allErrs = append(allErrs, field.TooLong(fldPath.Child("subresource"), fields.Subresource, MaxSubresourceNameLength)) 242 } 243 } 244 return allErrs 245 } 246 247 func ValidateConditions(conditions []metav1.Condition, fldPath *field.Path) field.ErrorList { 248 var allErrs field.ErrorList 249 250 conditionTypeToFirstIndex := map[string]int{} 251 for i, condition := range conditions { 252 if _, ok := conditionTypeToFirstIndex[condition.Type]; ok { 253 allErrs = append(allErrs, field.Duplicate(fldPath.Index(i).Child("type"), condition.Type)) 254 } else { 255 conditionTypeToFirstIndex[condition.Type] = i 256 } 257 258 allErrs = append(allErrs, ValidateCondition(condition, fldPath.Index(i))...) 259 } 260 261 return allErrs 262 } 263 264 // validConditionStatuses is used internally to check validity and provide a good message 265 var validConditionStatuses = sets.NewString(string(metav1.ConditionTrue), string(metav1.ConditionFalse), string(metav1.ConditionUnknown)) 266 267 const ( 268 maxReasonLen = 1 * 1024 269 maxMessageLen = 32 * 1024 270 ) 271 272 func ValidateCondition(condition metav1.Condition, fldPath *field.Path) field.ErrorList { 273 var allErrs field.ErrorList 274 275 // type is set and is a valid format 276 allErrs = append(allErrs, ValidateLabelName(condition.Type, fldPath.Child("type"))...) 277 278 // status is set and is an accepted value 279 if !validConditionStatuses.Has(string(condition.Status)) { 280 allErrs = append(allErrs, field.NotSupported(fldPath.Child("status"), condition.Status, validConditionStatuses.List())) 281 } 282 283 if condition.ObservedGeneration < 0 { 284 allErrs = append(allErrs, field.Invalid(fldPath.Child("observedGeneration"), condition.ObservedGeneration, "must be greater than or equal to zero")) 285 } 286 287 if condition.LastTransitionTime.IsZero() { 288 allErrs = append(allErrs, field.Required(fldPath.Child("lastTransitionTime"), "must be set")) 289 } 290 291 if len(condition.Reason) == 0 { 292 allErrs = append(allErrs, field.Required(fldPath.Child("reason"), "must be set")) 293 } else { 294 for _, currErr := range isValidConditionReason(condition.Reason) { 295 allErrs = append(allErrs, field.Invalid(fldPath.Child("reason"), condition.Reason, currErr)) 296 } 297 if len(condition.Reason) > maxReasonLen { 298 allErrs = append(allErrs, field.TooLong(fldPath.Child("reason"), condition.Reason, maxReasonLen)) 299 } 300 } 301 302 if len(condition.Message) > maxMessageLen { 303 allErrs = append(allErrs, field.TooLong(fldPath.Child("message"), condition.Message, maxMessageLen)) 304 } 305 306 return allErrs 307 } 308 309 const conditionReasonFmt string = "[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?" 310 const conditionReasonErrMsg string = "a condition reason must start with alphabetic character, optionally followed by a string of alphanumeric characters or '_,:', and must end with an alphanumeric character or '_'" 311 312 var conditionReasonRegexp = regexp.MustCompile("^" + conditionReasonFmt + "$") 313 314 // isValidConditionReason tests for a string that conforms to rules for condition reasons. This checks the format, but not the length. 315 func isValidConditionReason(value string) []string { 316 if !conditionReasonRegexp.MatchString(value) { 317 return []string{validation.RegexError(conditionReasonErrMsg, conditionReasonFmt, "my_name", "MY_NAME", "MyName", "ReasonA,ReasonB", "ReasonA:ReasonB")} 318 } 319 return nil 320 }