1
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
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
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
57 func ValidateStorageVersionUpdate(sv, oldSV *apiserverinternal.StorageVersion) field.ErrorList {
58
59 return field.ErrorList{}
60 }
61
62
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
160
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
184
185
186
187
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 }
222
View as plain text