k8s.io/kubernetes@v1.29.3/pkg/apis/apiserverinternal/validation/validation.go (about) 1 /* 2 Copyright 2020 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 "strings" 22 23 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 24 "k8s.io/apimachinery/pkg/util/sets" 25 utilvalidation "k8s.io/apimachinery/pkg/util/validation" 26 "k8s.io/apimachinery/pkg/util/validation/field" 27 "k8s.io/kubernetes/pkg/apis/apiserverinternal" 28 apivalidation "k8s.io/kubernetes/pkg/apis/core/validation" 29 ) 30 31 // ValidateStorageVersion validate the storage version object. 32 func ValidateStorageVersion(sv *apiserverinternal.StorageVersion) field.ErrorList { 33 var allErrs field.ErrorList 34 allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&sv.ObjectMeta, false, ValidateStorageVersionName, field.NewPath("metadata"))...) 35 allErrs = append(allErrs, validateStorageVersionStatus(sv.Status, field.NewPath("status"))...) 36 return allErrs 37 } 38 39 // ValidateStorageVersionName is a ValidateNameFunc for storage version names 40 func ValidateStorageVersionName(name string, prefix bool) []string { 41 var allErrs []string 42 idx := strings.LastIndex(name, ".") 43 if idx < 0 { 44 allErrs = append(allErrs, "name must be in the form of <group>.<resource>") 45 } else { 46 for _, msg := range utilvalidation.IsDNS1123Subdomain(name[:idx]) { 47 allErrs = append(allErrs, "the group segment "+msg) 48 } 49 for _, msg := range utilvalidation.IsDNS1035Label(name[idx+1:]) { 50 allErrs = append(allErrs, "the resource segment "+msg) 51 } 52 } 53 return allErrs 54 } 55 56 // ValidateStorageVersionUpdate tests if an update to a StorageVersion is valid. 57 func ValidateStorageVersionUpdate(sv, oldSV *apiserverinternal.StorageVersion) field.ErrorList { 58 // no error since StorageVersionSpec is an empty spec 59 return field.ErrorList{} 60 } 61 62 // ValidateStorageVersionStatusUpdate tests if an update to a StorageVersionStatus is valid. 63 func ValidateStorageVersionStatusUpdate(sv, oldSV *apiserverinternal.StorageVersion) field.ErrorList { 64 var allErrs field.ErrorList 65 allErrs = append(allErrs, validateStorageVersionStatus(sv.Status, field.NewPath("status"))...) 66 return allErrs 67 } 68 69 func validateStorageVersionStatus(ss apiserverinternal.StorageVersionStatus, fldPath *field.Path) field.ErrorList { 70 var allErrs field.ErrorList 71 allAPIServerIDs := sets.New[string]() 72 for i, ssv := range ss.StorageVersions { 73 if allAPIServerIDs.Has(ssv.APIServerID) { 74 allErrs = append(allErrs, field.Duplicate(fldPath.Child("storageVersions").Index(i).Child("apiServerID"), ssv.APIServerID)) 75 } else { 76 allAPIServerIDs.Insert(ssv.APIServerID) 77 } 78 allErrs = append(allErrs, validateServerStorageVersion(ssv, fldPath.Child("storageVersions").Index(i))...) 79 } 80 if err := validateCommonVersion(ss, fldPath); err != nil { 81 allErrs = append(allErrs, err) 82 } 83 allErrs = append(allErrs, validateStorageVersionCondition(ss.Conditions, fldPath)...) 84 return allErrs 85 } 86 87 func validateServerStorageVersion(ssv apiserverinternal.ServerStorageVersion, fldPath *field.Path) field.ErrorList { 88 allErrs := field.ErrorList{} 89 for _, msg := range apimachineryvalidation.NameIsDNSSubdomain(ssv.APIServerID, false) { 90 allErrs = append(allErrs, field.Invalid(fldPath.Child("apiServerID"), ssv.APIServerID, msg)) 91 } 92 if errs := isValidAPIVersion(ssv.EncodingVersion); len(errs) > 0 { 93 allErrs = append(allErrs, field.Invalid(fldPath.Child("encodingVersion"), ssv.EncodingVersion, strings.Join(errs, ","))) 94 } 95 96 found := false 97 for i, dv := range ssv.DecodableVersions { 98 if errs := isValidAPIVersion(dv); len(errs) > 0 { 99 allErrs = append(allErrs, field.Invalid(fldPath.Child("decodableVersions").Index(i), dv, strings.Join(errs, ","))) 100 } 101 if dv == ssv.EncodingVersion { 102 found = true 103 } 104 } 105 if !found { 106 allErrs = append(allErrs, field.Invalid(fldPath.Child("decodableVersions"), ssv.DecodableVersions, fmt.Sprintf("decodableVersions must include encodingVersion %s", ssv.EncodingVersion))) 107 } 108 109 for i, sv := range ssv.ServedVersions { 110 if errs := isValidAPIVersion(sv); len(errs) > 0 { 111 allErrs = append(allErrs, field.Invalid(fldPath.Child("servedVersions").Index(i), sv, strings.Join(errs, ","))) 112 } 113 foundDecodableVersion := false 114 for _, dv := range ssv.DecodableVersions { 115 if sv == dv { 116 foundDecodableVersion = true 117 break 118 } 119 } 120 if !foundDecodableVersion { 121 allErrs = append(allErrs, field.Invalid(fldPath.Child("servedVersions").Index(i), sv, fmt.Sprintf("individual served version : %s must be included in decodableVersions : %s", sv, ssv.DecodableVersions))) 122 } 123 } 124 return allErrs 125 } 126 127 func commonVersion(ssv []apiserverinternal.ServerStorageVersion) *string { 128 if len(ssv) == 0 { 129 return nil 130 } 131 commonVersion := ssv[0].EncodingVersion 132 for _, v := range ssv[1:] { 133 if v.EncodingVersion != commonVersion { 134 return nil 135 } 136 } 137 return &commonVersion 138 } 139 140 func validateCommonVersion(svs apiserverinternal.StorageVersionStatus, fldPath *field.Path) *field.Error { 141 actualCommonVersion := commonVersion(svs.StorageVersions) 142 if actualCommonVersion == nil && svs.CommonEncodingVersion == nil { 143 return nil 144 } 145 if actualCommonVersion == nil && svs.CommonEncodingVersion != nil { 146 return field.Invalid(fldPath.Child("commonEncodingVersion"), *svs.CommonEncodingVersion, "should be nil if servers do not agree on the same encoding version, or if there is no server reporting the supported versions yet") 147 } 148 if actualCommonVersion != nil && svs.CommonEncodingVersion == nil { 149 return field.Invalid(fldPath.Child("commonEncodingVersion"), svs.CommonEncodingVersion, fmt.Sprintf("the common encoding version is %s", *actualCommonVersion)) 150 } 151 if actualCommonVersion != nil && svs.CommonEncodingVersion != nil && *actualCommonVersion != *svs.CommonEncodingVersion { 152 return field.Invalid(fldPath.Child("commonEncodingVersion"), *svs.CommonEncodingVersion, fmt.Sprintf("the actual common encoding version is %s", *actualCommonVersion)) 153 } 154 return nil 155 } 156 157 func validateStorageVersionCondition(conditions []apiserverinternal.StorageVersionCondition, fldPath *field.Path) field.ErrorList { 158 allErrs := field.ErrorList{} 159 // We do not verify that the condition type or the condition status is 160 // a predefined one because we might add more type or status later. 161 seenType := make(map[apiserverinternal.StorageVersionConditionType]int) 162 for i, condition := range conditions { 163 if ii, ok := seenType[condition.Type]; ok { 164 allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("type"), string(condition.Type), 165 fmt.Sprintf("the type of the condition is not unique, it also appears in conditions[%d]", ii))) 166 } 167 seenType[condition.Type] = i 168 allErrs = append(allErrs, apivalidation.ValidateQualifiedName(string(condition.Type), fldPath.Index(i).Child("type"))...) 169 allErrs = append(allErrs, apivalidation.ValidateQualifiedName(string(condition.Status), fldPath.Index(i).Child("status"))...) 170 if condition.Reason == "" { 171 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("reason"), "reason cannot be empty")) 172 } 173 if condition.Message == "" { 174 allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("message"), "message cannot be empty")) 175 } 176 } 177 return allErrs 178 } 179 180 const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?" 181 const dns1035LabelErrMsg string = "a DNS-1035 label, which must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character" 182 183 // isValidAPIVersion tests whether the value passed is a valid apiVersion. A 184 // valid apiVersion contains a version string that matches DNS_LABEL format, 185 // with an optional group/ prefix, where the group string matches DNS_SUBDOMAIN 186 // format. If the value is not valid, a list of error strings is returned. 187 // Otherwise an empty list (or nil) is returned. 188 func isValidAPIVersion(apiVersion string) []string { 189 var errs []string 190 parts := strings.Split(apiVersion, "/") 191 var version string 192 switch len(parts) { 193 case 1: 194 version = parts[0] 195 case 2: 196 var group string 197 group, version = parts[0], parts[1] 198 if len(group) == 0 { 199 errs = append(errs, "group part: "+utilvalidation.EmptyError()) 200 } else if msgs := utilvalidation.IsDNS1123Subdomain(group); len(msgs) != 0 { 201 errs = append(errs, prefixEach(msgs, "group part: ")...) 202 } 203 default: 204 return append(errs, "an apiVersion is "+utilvalidation.RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123")+ 205 " with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyVersion')") 206 } 207 208 if len(version) == 0 { 209 errs = append(errs, "version part: "+utilvalidation.EmptyError()) 210 } else if msgs := utilvalidation.IsDNS1035Label(version); len(msgs) != 0 { 211 errs = append(errs, prefixEach(msgs, "version part: ")...) 212 } 213 return errs 214 } 215 216 func prefixEach(msgs []string, prefix string) []string { 217 for i := range msgs { 218 msgs[i] = prefix + msgs[i] 219 } 220 return msgs 221 }