2 Copyright 2014 The Kubernetes Authors.
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
8 http://www.apache.org/licenses/LICENSE-2.0
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.
27 "k8s.io/apimachinery/pkg/util/validation/field"
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
36 var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$")
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 {
44 parts := strings.Split(value, "/")
51 prefix, name = parts[0], parts[1]
53 errs = append(errs, "prefix part "+EmptyError())
54 } else if msgs := IsDNS1123Subdomain(prefix); len(msgs) != 0 {
55 errs = append(errs, prefixEach(msgs, "prefix part ")...)
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')")
63 errs = append(errs, "name part "+EmptyError())
64 } else if len(name) > qualifiedNameMaxLength {
65 errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength))
67 if !qualifiedNameRegexp.MatchString(name) {
68 errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc"))
73 // IsFullyQualifiedName checks if the name is fully qualified.
74 func IsFullyQualifiedName(fldPath *field.Path, name string) field.ErrorList {
75 var allErrors field.ErrorList
77 return append(allErrors, field.Required(fldPath, ""))
79 if errs := IsDNS1123Subdomain(name); len(errs) > 0 {
80 return append(allErrors, field.Invalid(fldPath, name, strings.Join(errs, ",")))
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"))
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
92 var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$")
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 {
99 if len(value) > LabelValueMaxLength {
100 errs = append(errs, MaxLenError(LabelValueMaxLength))
102 if !labelValueRegexp.MatchString(value) {
103 errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345"))
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
112 var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$")
114 // IsDNS1123Label tests for a string that conforms to the definition of a label in
116 func IsDNS1123Label(value string) []string {
118 if len(value) > DNS1123LabelMaxLength {
119 errs = append(errs, MaxLenError(DNS1123LabelMaxLength))
121 if !dns1123LabelRegexp.MatchString(value) {
122 errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc"))
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
131 var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$")
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 {
137 if len(value) > DNS1123SubdomainMaxLength {
138 errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
140 if !dns1123SubdomainRegexp.MatchString(value) {
141 errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com"))
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
150 var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$")
152 // IsDNS1035Label tests for a string that conforms to the definition of a label in
154 func IsDNS1035Label(value string) []string {
156 if len(value) > DNS1035LabelMaxLength {
157 errs = append(errs, MaxLenError(DNS1035LabelMaxLength))
159 if !dns1035LabelRegexp.MatchString(value) {
160 errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123"))
165 // wildcard definition - RFC 1034 section 4.3.3.
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"
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 + "$")
178 if len(value) > DNS1123SubdomainMaxLength {
179 errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
181 if !wildcardDNS1123SubdomainRegexp.MatchString(value) {
182 errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com"))
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 '_'"
190 var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$")
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")}
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 {
206 return []string{InclusiveRangeError(1, 65535)}
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 {
214 return []string{InclusiveRangeError(min, max)}
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.
221 maxUserID = math.MaxInt32
223 maxGroupID = math.MaxInt32
226 // IsValidGroupID tests that the argument is a valid Unix GID.
227 func IsValidGroupID(gid int64) []string {
228 if minGroupID <= gid && gid <= maxGroupID {
231 return []string{InclusiveRangeError(minGroupID, maxGroupID)}
234 // IsValidUserID tests that the argument is a valid Unix UID.
235 func IsValidUserID(uid int64) []string {
236 if minUserID <= uid && uid <= maxUserID {
239 return []string{InclusiveRangeError(minUserID, maxUserID)}
242 var portNameCharsetRegex = regexp.MustCompile("^[-a-z0-9]+$")
243 var portNameOneLetterRegexp = regexp.MustCompile("[a-z]")
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.
250 // Note: We only allow lower-case characters, even though RFC 6335 is case
252 func IsValidPortName(port string) []string {
255 errs = append(errs, MaxLenError(15))
257 if !portNameCharsetRegex.MatchString(port) {
258 errs = append(errs, "must contain only alpha-numeric characters (a-z, 0-9), and hyphens (-)")
260 if !portNameOneLetterRegexp.MatchString(port) {
261 errs = append(errs, "must contain at least one letter or number (a-z, 0-9)")
263 if strings.Contains(port, "--") {
264 errs = append(errs, "must not contain consecutive hyphens")
266 if len(port) > 0 && (port[0] == '-' || port[len(port)-1] == '-') {
267 errs = append(errs, "must not begin or end with a hyphen")
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)"}
280 const percentFmt string = "[0-9]+%"
281 const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'"
283 var percentRegexp = regexp.MustCompile("^" + percentFmt + "$")
285 func IsValidPercent(percent string) []string {
286 if !percentRegexp.MatchString(percent) {
287 return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")}
292 const httpHeaderNameFmt string = "[-A-Za-z0-9]+"
293 const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'"
295 var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$")
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")}
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"
309 var envVarNameRegexp = regexp.MustCompile("^" + envVarNameFmt + "$")
311 // IsEnvVarName tests if a string is a valid environment variable name.
312 func IsEnvVarName(value string) []string {
314 if !envVarNameRegexp.MatchString(value) {
315 errs = append(errs, RegexError(envVarNameFmtErrMsg, envVarNameFmt, "my.env-name", "MY_ENV.NAME", "MyEnvName1"))
318 errs = append(errs, hasChDirPrefix(value)...)
322 const configMapKeyFmt = `[-._a-zA-Z0-9]+`
323 const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'"
325 var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$")
327 // IsConfigMapKey tests for a string that is a valid key for a ConfigMap or Secret
328 func IsConfigMapKey(value string) []string {
330 if len(value) > DNS1123SubdomainMaxLength {
331 errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength))
333 if !configMapKeyRegexp.MatchString(value) {
334 errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name"))
336 errs = append(errs, hasChDirPrefix(value)...)
340 // MaxLenError returns a string explanation of a "string too long" validation
342 func MaxLenError(length int) string {
343 return fmt.Sprintf("must be no more than %d characters", length)
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 + "')"
352 for i := range examples {
356 msg += "'" + examples[i] + "', "
358 msg += "regex used for validation is '" + fmt + "')"
362 // EmptyError returns a string explanation of a "must not be empty" validation
364 func EmptyError() string {
365 return "must be non-empty"
368 func prefixEach(msgs []string, prefix string) []string {
369 for i := range msgs {
370 msgs[i] = prefix + msgs[i]
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)
381 func hasChDirPrefix(value string) []string {
385 errs = append(errs, `must not be '.'`)
387 errs = append(errs, `must not be '..'`)
388 case strings.HasPrefix(value, ".."):
389 errs = append(errs, `must not start with '..'`)
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 {
398 ip, port, err := net.SplitHostPort(value)
400 return append(errs, "must be a valid socket address format, (e.g. 0.0.0.0:10254 or [::]:10254)")
403 portInt, _ := strconv.Atoi(port)
404 errs = append(errs, IsValidPortNum(portInt)...)
405 errs = append(errs, IsValidIP(ip)...)