Code refactoring for bpa operator
[icn.git] / cmd / bpa-operator / vendor / k8s.io / apimachinery / pkg / util / validation / validation.go
1 /*
2 Copyright 2014 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         "math"
22         "net"
23         "regexp"
24         "strconv"
25         "strings"
26
27         "k8s.io/apimachinery/pkg/util/validation/field"
28 )
29
30 const qnameCharFmt string = "[A-Za-z0-9]"
31 const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
32 const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
33 const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
34 const qualifiedNameMaxLength int = 63
35
36 var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
37
38 // IsQualifiedName tests whether the value passed is what Kubernetes calls a
39 // "qualified name".  This is a format used in various places throughout the
40 // system.  If the value is not valid, a list of error strings is returned.
41 // Otherwise an empty list (or nil) is returned.
42 func IsQualifiedName(value string) []string {
43         var errs []string
44         parts := strings.Split(value, "/")
45         var name string
46         switch len(parts) {
47         case 1:
48                 name = parts[0]
49         case 2:
50                 var prefix string
51                 prefix, name = parts[0], parts[1]
52                 if len(prefix) == 0 {
53                         errs = append(errs, "prefix part "+EmptyError())
54                 } else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
55                         errs = append(errs, prefixEach(msgs, "prefix part ")...)
56                 }
57         default:
58                 return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+
59                         " with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')")
60         }
61
62         if len(name) == 0 {
63                 errs = append(errs, "name part "+EmptyError())
64         } else if len(name) > qualifiedNameMaxLength {
65                 errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
66         }
67         if !qualifiedNameRegexp.MatchString(name) {
68                 errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
69         }
70         return errs
71 }
72
73 // IsFullyQualifiedName checks if the name is fully qualified.
74 func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList {
75         var allErrors field.ErrorList
76         if len(name) == 0 {
77                 return append(allErrors, field.Required(fldPath, ""))
78         }
79         if errs := IsDNS1123Subdomain(name); len(errs) > 0 {
80                 return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ",")))
81         }
82         if len(strings.Split(name, ".")) < 3 {
83                 return append(allErrors, field.Invalid(fldPath, name, "should be a domain with at least three segments separated by dots"))
84         }
85         return allErrors
86 }
87
88 const labelValueFmt string = "(" + qualifiedNameFmt + ")?"
89 const labelValueErrMsg string = "a valid label must be an empty string or consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character"
90 const LabelValueMaxLength int = 63
91
92 var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
93
94 // IsValidLabelValue tests whether the value passed is a valid label value.  If
95 // the value is not valid, a list of error strings is returned.  Otherwise an
96 // empty list (or nil) is returned.
97 func IsValidLabelValue(value string) []string {
98         var errs []string
99         if len(value) > LabelValueMaxLength {
100                 errs = append(errs, MaxLenError(LabelValueMaxLength))
101         }
102         if !labelValueRegexp.MatchString(value) {
103                 errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
104         }
105         return errs
106 }
107
108 const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
109 const dns1123LabelErrMsg string = "a DNS-1123 label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character"
110 const DNS1123LabelMaxLength int = 63
111
112 var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
113
114 // IsDNS1123Label tests for a string that conforms to the definition of a label in
115 // DNS (RFC 1123).
116 func IsDNS1123Label(value string) []string {
117         var errs []string
118         if len(value) > DNS1123LabelMaxLength {
119                 errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
120         }
121         if !dns1123LabelRegexp.MatchString(value) {
122                 errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
123         }
124         return errs
125 }
126
127 const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*"
128 const dns1123SubdomainErrorMsg string = "a DNS-1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
129 const DNS1123SubdomainMaxLength int = 253
130
131 var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
132
133 // IsDNS1123Subdomain tests for a string that conforms to the definition of a
134 // subdomain in DNS (RFC 1123).
135 func IsDNS1123Subdomain(value string) []string {
136         var errs []string
137         if len(value) > DNS1123SubdomainMaxLength {
138                 errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
139         }
140         if !dns1123SubdomainRegexp.MatchString(value) {
141                 errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
142         }
143         return errs
144 }
145
146 const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?"
147 const dns1035LabelErrMsg string = "a DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
148 const DNS1035LabelMaxLength int = 63
149
150 var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
151
152 // IsDNS1035Label tests for a string that conforms to the definition of a label in
153 // DNS (RFC 1035).
154 func IsDNS1035Label(value string) []string {
155         var errs []string
156         if len(value) > DNS1035LabelMaxLength {
157                 errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
158         }
159         if !dns1035LabelRegexp.MatchString(value) {
160                 errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
161         }
162         return errs
163 }
164
165 // wildcard definition - RFC 1034 section 4.3.3.
166 // examples:
167 // - valid: *.bar.com, *.foo.bar.com
168 // - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, *
169 const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt
170 const wildcardDNS1123SubdomainErrMsg = "a wildcard DNS-1123 subdomain must start with '*.', followed by a valid DNS subdomain, which must consist of lower case alphanumeric characters, '-' or '.' and end with an alphanumeric character"
171
172 // IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a
173 // wildcard subdomain in DNS (RFC 1034 section 4.3.3).
174 func IsWildcardDNS1123Subdomain(value string) []string {
175         wildcardDNS1123SubdomainRegexp := regexp.MustCompile("^" + wildcardDNS1123SubdomainFmt + "$")
176
177         var errs []string
178         if len(value) > DNS1123SubdomainMaxLength {
179                 errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
180         }
181         if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
182                 errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
183         }
184         return errs
185 }
186
187 const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*"
188 const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'"
189
190 var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$")
191
192 // IsCIdentifier tests for a string that conforms the definition of an identifier
193 // in C. This checks the format, but not the length.
194 func IsCIdentifier(value string) []string {
195         if !cIdentifierRegexp.MatchString(value) {
196                 return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")}
197         }
198         return nil
199 }
200
201 // IsValidPortNum tests that the argument is a valid, non-zero port number.
202 func IsValidPortNum(port int) []string {
203         if 1 <= port && port <= 65535 {
204                 return nil
205         }
206         return []string{InclusiveRangeError(1, 65535)}
207 }
208
209 // IsInRange tests that the argument is in an inclusive range.
210 func IsInRange(value int, min int, max int) []string {
211         if value >= min && value <= max {
212                 return nil
213         }
214         return []string{InclusiveRangeError(min, max)}
215 }
216
217 // Now in libcontainer UID/GID limits is 0 ~ 1<<31 - 1
218 // TODO: once we have a type for UID/GID we should make these that type.
219 const (
220         minUserID  = 0
221         maxUserID  = math.MaxInt32
222         minGroupID = 0
223         maxGroupID = math.MaxInt32
224 )
225
226 // IsValidGroupID tests that the argument is a valid Unix GID.
227 func IsValidGroupID(gid int64) []string {
228         if minGroupID <= gid && gid <= maxGroupID {
229                 return nil
230         }
231         return []string{InclusiveRangeError(minGroupID, maxGroupID)}
232 }
233
234 // IsValidUserID tests that the argument is a valid Unix UID.
235 func IsValidUserID(uid int64) []string {
236         if minUserID <= uid && uid <= maxUserID {
237                 return nil
238         }
239         return []string{InclusiveRangeError(minUserID, maxUserID)}
240 }
241
242 var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$")
243 var portNameOneLetterRegexp = regexp.MustCompile("[a-z]")
244
245 // IsValidPortName check that the argument is valid syntax. It must be
246 // non-empty and no more than 15 characters long. It may contain only [-a-z0-9]
247 // and must contain at least one letter [a-z]. It must not start or end with a
248 // hyphen, nor contain adjacent hyphens.
249 //
250 // Note: We only allow lower-case characters, even though RFC 6335 is case
251 // insensitive.
252 func IsValidPortName(port string) []string {
253         var errs []string
254         if len(port) > 15 {
255                 errs = append(errs, MaxLenError(15))
256         }
257         if !portNameCharsetRegex.MatchString(port) {
258                 errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)")
259         }
260         if !portNameOneLetterRegexp.MatchString(port) {
261                 errs = append(errs, "must contain at least one letter or number (a-z, 0-9)")
262         }
263         if strings.Contains(port, "--") {
264                 errs = append(errs, "must not contain consecutive hyphens")
265         }
266         if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') {
267                 errs = append(errs, "must not begin or end with a hyphen")
268         }
269         return errs
270 }
271
272 // IsValidIP tests that the argument is a valid IP address.
273 func IsValidIP(value string) []string {
274         if net.ParseIP(value) == nil {
275                 return []string{"must be a valid IP address, (e.g. 10.9.8.7)"}
276         }
277         return nil
278 }
279
280 const percentFmt string = "[0-9]+%"
281 const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'"
282
283 var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
284
285 func IsValidPercent(percent string) []string {
286         if !percentRegexp.MatchString(percent) {
287                 return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")}
288         }
289         return nil
290 }
291
292 const httpHeaderNameFmt string = "[-A-Za-z0-9]+"
293 const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'"
294
295 var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$")
296
297 // IsHTTPHeaderName checks that a string conforms to the Go HTTP library's
298 // definition of a valid header field name (a stricter subset than RFC7230).
299 func IsHTTPHeaderName(value string) []string {
300         if !httpHeaderNameRegexp.MatchString(value) {
301                 return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")}
302         }
303         return nil
304 }
305
306 const envVarNameFmt = "[-._a-zA-Z][-._a-zA-Z0-9]*"
307 const envVarNameFmtErrMsg string = "a valid environment variable name must consist of alphabetic characters, digits, '_', '-', or '.', and must not start with a digit"
308
309 var envVarNameRegexp = regexp.MustCompile("^" + envVarNameFmt + "$")
310
311 // IsEnvVarName tests if a string is a valid environment variable name.
312 func IsEnvVarName(value string) []string {
313         var errs []string
314         if !envVarNameRegexp.MatchString(value) {
315                 errs = append(errs, RegexError(envVarNameFmtErrMsg, envVarNameFmt, "my.env-name", "MY_ENV.NAME", "MyEnvName1"))
316         }
317
318         errs = append(errs, hasChDirPrefix(value)...)
319         return errs
320 }
321
322 const configMapKeyFmt = `[-._a-zA-Z0-9]+`
323 const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'"
324
325 var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$")
326
327 // IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret
328 func IsConfigMapKey(value string) []string {
329         var errs []string
330         if len(value) > DNS1123SubdomainMaxLength {
331                 errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
332         }
333         if !configMapKeyRegexp.MatchString(value) {
334                 errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name"))
335         }
336         errs = append(errs, hasChDirPrefix(value)...)
337         return errs
338 }
339
340 // MaxLenError returns a string explanation of a "string too long" validation
341 // failure.
342 func MaxLenError(length int) string {
343         return fmt.Sprintf("must be no more than %d characters", length)
344 }
345
346 // RegexError returns a string explanation of a regex validation failure.
347 func RegexError(msg string, fmt string, examples ...string) string {
348         if len(examples) == 0 {
349                 return msg + " (regex used for validation is '" + fmt + "')"
350         }
351         msg += " (e.g. "
352         for i := range examples {
353                 if i > 0 {
354                         msg += " or "
355                 }
356                 msg += "'" + examples[i] + "', "
357         }
358         msg += "regex used for validation is '" + fmt + "')"
359         return msg
360 }
361
362 // EmptyError returns a string explanation of a "must not be empty" validation
363 // failure.
364 func EmptyError() string {
365         return "must be non-empty"
366 }
367
368 func prefixEach(msgs []string, prefix string) []string {
369         for i := range msgs {
370                 msgs[i] = prefix + msgs[i]
371         }
372         return msgs
373 }
374
375 // InclusiveRangeError returns a string explanation of a numeric "must be
376 // between" validation failure.
377 func InclusiveRangeError(lo, hi int) string {
378         return fmt.Sprintf(`must be between %d and %d, inclusive`, lo, hi)
379 }
380
381 func hasChDirPrefix(value string) []string {
382         var errs []string
383         switch {
384         case value == ".":
385                 errs = append(errs, `must not be '.'`)
386         case value == "..":
387                 errs = append(errs, `must not be '..'`)
388         case strings.HasPrefix(value, ".."):
389                 errs = append(errs, `must not start with '..'`)
390         }
391         return errs
392 }
393
394 // IsSocketAddr checks that a string conforms is a valid socket address
395 // as defined in RFC 789. (e.g 0.0.0.0:10254 or [::]:10254))
396 func IsValidSocketAddr(value string) []string {
397         var errs []string
398         ip, port, err := net.SplitHostPort(value)
399         if err != nil {
400                 return append(errs, "must be a valid socket address format, (e.g. 0.0.0.0:10254 or [::]:10254)")
401                 return errs
402         }
403         portInt, _ := strconv.Atoi(port)
404         errs = append(errs, IsValidPortNum(portInt)...)
405         errs = append(errs, IsValidIP(ip)...)
406         return errs
407 }