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