diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index 239af9ad81..4f86aca53a 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -36,6 +36,17 @@ import ( "k8s.io/kubernetes/pkg/util/validation/field" ) +const ( + dnsLabelErrMsg = "a valid DNS (RFC 1123) label must consist of" + dnsSubdomainLabelErrMsg = "a valid DNS (RFC 1123) subdomain" + labelErrMsg = "a valid label must be an empty string or consist of" + lowerCaseLabelErrMsg = "a valid label must consist of" + maxLengthErrMsg = "must be no more than" + namePartErrMsg = "name part must consist of" + nameErrMsg = "a qualified name must consist of" + idErrMsg = "a valid C identifier must" +) + func expectPrefix(t *testing.T, prefix string, errs field.ErrorList) { for i := range errs { if f, p := errs[i].Field, prefix; !strings.HasPrefix(f, p) { @@ -458,10 +469,10 @@ func TestValidateAnnotations(t *testing.T) { annotations map[string]string expect string }{ - {map[string]string{"nospecialchars^=@": "bar"}, "must match the regex"}, - {map[string]string{"cantendwithadash-": "bar"}, "must match the regex"}, - {map[string]string{"only/one/slash": "bar"}, "must match the regex"}, - {map[string]string{strings.Repeat("a", 254): "bar"}, "must be no more than"}, + {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, + {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, + {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, + {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, } for i := range nameErrorCases { errs := ValidateAnnotations(nameErrorCases[i].annotations, field.NewPath("field")) @@ -1152,7 +1163,7 @@ func TestValidateVolumes(t *testing.T) { }, errtype: field.ErrorTypeInvalid, errfield: "name", - errdetail: "must match the regex", + errdetail: dnsLabelErrMsg, }, // More than one source field specified. { @@ -2530,7 +2541,7 @@ func TestValidateEnv(t *testing.T) { { name: "name not a C identifier", envs: []api.EnvVar{{Name: "a.b.c"}}, - expectedError: `[0].name: Invalid value: "a.b.c": must match the regex`, + expectedError: `[0].name: Invalid value: "a.b.c": ` + idErrMsg, }, { name: "value and valueFrom specified", @@ -7288,11 +7299,11 @@ func TestValidateLimitRange(t *testing.T) { }, "invalid-name": { api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: api.LimitRangeSpec{}}, - "must match the regex", + dnsSubdomainLabelErrMsg, }, "invalid-namespace": { api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: api.LimitRangeSpec{}}, - "must match the regex", + dnsLabelErrMsg, }, "duplicate-limit-type": { api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ @@ -7634,11 +7645,11 @@ func TestValidateResourceQuota(t *testing.T) { }, "invalid Name": { api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "^Invalid", Namespace: "foo"}, Spec: spec}, - "must match the regex", + dnsSubdomainLabelErrMsg, }, "invalid Namespace": { api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "^Invalid"}, Spec: spec}, - "must match the regex", + dnsLabelErrMsg, }, "negative-limits": { api.ResourceQuota{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: negativeSpec}, @@ -8232,12 +8243,12 @@ func TestValidateEndpoints(t *testing.T) { "invalid namespace": { endpoints: api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "mysvc", Namespace: "no@#invalid.;chars\"allowed"}}, errorType: "FieldValueInvalid", - errorDetail: "must match the regex", + errorDetail: dnsLabelErrMsg, }, "invalid name": { endpoints: api.Endpoints{ObjectMeta: api.ObjectMeta{Name: "-_Invliad^&Characters", Namespace: "namespace"}}, errorType: "FieldValueInvalid", - errorDetail: "must match the regex", + errorDetail: dnsSubdomainLabelErrMsg, }, "empty addresses": { endpoints: api.Endpoints{ diff --git a/pkg/apis/extensions/validation/validation_test.go b/pkg/apis/extensions/validation/validation_test.go index 86bf008fc7..cb0ca2a98c 100644 --- a/pkg/apis/extensions/validation/validation_test.go +++ b/pkg/apis/extensions/validation/validation_test.go @@ -750,7 +750,7 @@ func TestValidateDeployment(t *testing.T) { MaxSurge: intstr.FromString("20Percent"), }, } - errorCases["must match the regex"] = invalidMaxSurgeDeployment + errorCases["a valid percent string must be"] = invalidMaxSurgeDeployment // MaxSurge and MaxUnavailable cannot both be zero. invalidRollingUpdateDeployment := validDeployment() diff --git a/pkg/apis/meta/v1/validation/validation_test.go b/pkg/apis/meta/v1/validation/validation_test.go index 72b151a4c7..0e8f55890a 100644 --- a/pkg/apis/meta/v1/validation/validation_test.go +++ b/pkg/apis/meta/v1/validation/validation_test.go @@ -47,14 +47,19 @@ func TestValidateLabels(t *testing.T) { } } + namePartErrMsg := "name part must consist of" + nameErrMsg := "a qualified name must consist of" + labelErrMsg := "a valid label must be an empty string or consist of" + maxLengthErrMsg := "must be no more than" + labelNameErrorCases := []struct { labels map[string]string expect string }{ - {map[string]string{"nospecialchars^=@": "bar"}, "must match the regex"}, - {map[string]string{"cantendwithadash-": "bar"}, "must match the regex"}, - {map[string]string{"only/one/slash": "bar"}, "must match the regex"}, - {map[string]string{strings.Repeat("a", 254): "bar"}, "must be no more than"}, + {map[string]string{"nospecialchars^=@": "bar"}, namePartErrMsg}, + {map[string]string{"cantendwithadash-": "bar"}, namePartErrMsg}, + {map[string]string{"only/one/slash": "bar"}, nameErrMsg}, + {map[string]string{strings.Repeat("a", 254): "bar"}, maxLengthErrMsg}, } for i := range labelNameErrorCases { errs := ValidateLabels(labelNameErrorCases[i].labels, field.NewPath("field")) @@ -71,10 +76,10 @@ func TestValidateLabels(t *testing.T) { labels map[string]string expect string }{ - {map[string]string{"toolongvalue": strings.Repeat("a", 64)}, "must be no more than"}, - {map[string]string{"backslashesinvalue": "some\\bad\\value"}, "must match the regex"}, - {map[string]string{"nocommasallowed": "bad,value"}, "must match the regex"}, - {map[string]string{"strangecharsinvalue": "?#$notsogood"}, "must match the regex"}, + {map[string]string{"toolongvalue": strings.Repeat("a", 64)}, maxLengthErrMsg}, + {map[string]string{"backslashesinvalue": "some\\bad\\value"}, labelErrMsg}, + {map[string]string{"nocommasallowed": "bad,value"}, labelErrMsg}, + {map[string]string{"strangecharsinvalue": "?#$notsogood"}, labelErrMsg}, } for i := range labelValueErrorCases { errs := ValidateLabels(labelValueErrorCases[i].labels, field.NewPath("field")) diff --git a/pkg/util/validation/validation.go b/pkg/util/validation/validation.go index cc58101ba0..f49ac15783 100644 --- a/pkg/util/validation/validation.go +++ b/pkg/util/validation/validation.go @@ -27,6 +27,7 @@ import ( const qnameCharFmt string = "[A-Za-z0-9]" const qnameExtCharFmt string = "[-A-Za-z0-9_.]" const qualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt +const qualifiedNameErrMsg string = "must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character" const qualifiedNameMaxLength int = 63 var qualifiedNameRegexp = regexp.MustCompile("^" + qualifiedNameFmt + "$") @@ -51,8 +52,8 @@ func IsQualifiedName(value string) []string { errs = append(errs, prefixEach(msgs, "prefix part ")...) } default: - return append(errs, RegexError(qualifiedNameFmt, "MyName", "my.name", "123-abc")+ - " with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName'") + return append(errs, "a qualified name "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")+ + " with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')") } if len(name) == 0 { @@ -61,12 +62,13 @@ func IsQualifiedName(value string) []string { errs = append(errs, "name part "+MaxLenError(qualifiedNameMaxLength)) } if !qualifiedNameRegexp.MatchString(name) { - errs = append(errs, "name part "+RegexError(qualifiedNameFmt, "MyName", "my.name", "123-abc")) + errs = append(errs, "name part "+RegexError(qualifiedNameErrMsg, qualifiedNameFmt, "MyName", "my.name", "123-abc")) } return errs } const labelValueFmt string = "(" + qualifiedNameFmt + ")?" +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" const LabelValueMaxLength int = 63 var labelValueRegexp = regexp.MustCompile("^" + labelValueFmt + "$") @@ -80,12 +82,13 @@ func IsValidLabelValue(value string) []string { errs = append(errs, MaxLenError(LabelValueMaxLength)) } if !labelValueRegexp.MatchString(value) { - errs = append(errs, RegexError(labelValueFmt, "MyValue", "my_value", "12345")) + errs = append(errs, RegexError(labelValueErrMsg, labelValueFmt, "MyValue", "my_value", "12345")) } return errs } const dns1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?" +const dns1123LabelErrMsg string = "a valid DNS (RFC 1123) label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character" const DNS1123LabelMaxLength int = 63 var dns1123LabelRegexp = regexp.MustCompile("^" + dns1123LabelFmt + "$") @@ -98,12 +101,13 @@ func IsDNS1123Label(value string) []string { errs = append(errs, MaxLenError(DNS1123LabelMaxLength)) } if !dns1123LabelRegexp.MatchString(value) { - errs = append(errs, RegexError(dns1123LabelFmt, "my-name", "123-abc")) + errs = append(errs, RegexError(dns1123LabelErrMsg, dns1123LabelFmt, "my-name", "123-abc")) } return errs } const dns1123SubdomainFmt string = dns1123LabelFmt + "(\\." + dns1123LabelFmt + ")*" +const dns1123SubdomainErrorMsg string = "a valid DNS (RFC 1123) subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character" const DNS1123SubdomainMaxLength int = 253 var dns1123SubdomainRegexp = regexp.MustCompile("^" + dns1123SubdomainFmt + "$") @@ -116,12 +120,13 @@ func IsDNS1123Subdomain(value string) []string { errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) } if !dns1123SubdomainRegexp.MatchString(value) { - errs = append(errs, RegexError(dns1123SubdomainFmt, "example.com")) + errs = append(errs, RegexError(dns1123SubdomainErrorMsg, dns1123SubdomainFmt, "example.com")) } return errs } const dns1035LabelFmt string = "[a-z]([-a-z0-9]*[a-z0-9])?" +const dns1035LabelErrMsg string = "a valid DNS (RFC 1035) label must consist of lower case alphanumeric characters or '-', and must start and end with an alphanumeric character" const DNS1035LabelMaxLength int = 63 var dns1035LabelRegexp = regexp.MustCompile("^" + dns1035LabelFmt + "$") @@ -134,7 +139,7 @@ func IsDNS1035Label(value string) []string { errs = append(errs, MaxLenError(DNS1035LabelMaxLength)) } if !dns1035LabelRegexp.MatchString(value) { - errs = append(errs, RegexError(dns1035LabelFmt, "my-name", "abc-123")) + errs = append(errs, RegexError(dns1035LabelErrMsg, dns1035LabelFmt, "my-name", "abc-123")) } return errs } @@ -144,6 +149,7 @@ func IsDNS1035Label(value string) []string { // - valid: *.bar.com, *.foo.bar.com // - invalid: *.*.bar.com, *.foo.*.com, *bar.com, f*.bar.com, * const wildcardDNS1123SubdomainFmt = "\\*\\." + dns1123SubdomainFmt +const wildcardDNS1123SubdomainErrMsg = "a valid wildcard DNS (RFC 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" // IsWildcardDNS1123Subdomain tests for a string that conforms to the definition of a // wildcard subdomain in DNS (RFC 1034 section 4.3.3). @@ -155,12 +161,13 @@ func IsWildcardDNS1123Subdomain(value string) []string { errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) } if !wildcardDNS1123SubdomainRegexp.MatchString(value) { - errs = append(errs, RegexError(wildcardDNS1123SubdomainFmt, "*.example.com")) + errs = append(errs, RegexError(wildcardDNS1123SubdomainErrMsg, wildcardDNS1123SubdomainFmt, "*.example.com")) } return errs } const cIdentifierFmt string = "[A-Za-z_][A-Za-z0-9_]*" +const identifierErrMsg string = "a valid C identifier must start with alphabetic character or '_', followed by a string of alphanumeric characters or '_'" var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$") @@ -168,7 +175,7 @@ var cIdentifierRegexp = regexp.MustCompile("^" + cIdentifierFmt + "$") // in C. This checks the format, but not the length. func IsCIdentifier(value string) []string { if !cIdentifierRegexp.MatchString(value) { - return []string{RegexError(cIdentifierFmt, "my_name", "MY_NAME", "MyName")} + return []string{RegexError(identifierErrMsg, cIdentifierFmt, "my_name", "MY_NAME", "MyName")} } return nil } @@ -245,17 +252,19 @@ func IsValidIP(value string) []string { } const percentFmt string = "[0-9]+%" +const percentErrMsg string = "a valid percent string must be a numeric string followed by an ending '%'" var percentRegexp = regexp.MustCompile("^" + percentFmt + "$") func IsValidPercent(percent string) []string { if !percentRegexp.MatchString(percent) { - return []string{RegexError(percentFmt, "1%", "93%")} + return []string{RegexError(percentErrMsg, percentFmt, "1%", "93%")} } return nil } const httpHeaderNameFmt string = "[-A-Za-z0-9]+" +const httpHeaderNameErrMsg string = "a valid HTTP header must consist of alphanumeric characters or '-'" var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$") @@ -263,12 +272,13 @@ var httpHeaderNameRegexp = regexp.MustCompile("^" + httpHeaderNameFmt + "$") // definition of a valid header field name (a stricter subset than RFC7230). func IsHTTPHeaderName(value string) []string { if !httpHeaderNameRegexp.MatchString(value) { - return []string{RegexError(httpHeaderNameFmt, "X-Header-Name")} + return []string{RegexError(httpHeaderNameErrMsg, httpHeaderNameFmt, "X-Header-Name")} } return nil } const configMapKeyFmt = `[-._a-zA-Z0-9]+` +const configMapKeyErrMsg string = "a valid config key must consist of alphanumeric characters, '-', '_' or '.'" var configMapKeyRegexp = regexp.MustCompile("^" + configMapKeyFmt + "$") @@ -279,7 +289,7 @@ func IsConfigMapKey(value string) []string { errs = append(errs, MaxLenError(DNS1123SubdomainMaxLength)) } if !configMapKeyRegexp.MatchString(value) { - errs = append(errs, RegexError(configMapKeyFmt, "key.name", "KEY_NAME", "key-name")) + errs = append(errs, RegexError(configMapKeyErrMsg, configMapKeyFmt, "key.name", "KEY_NAME", "key-name")) } if value == "." { errs = append(errs, `must not be '.'`) @@ -298,19 +308,19 @@ func MaxLenError(length int) string { } // RegexError returns a string explanation of a regex validation failure. -func RegexError(fmt string, examples ...string) string { - s := "must match the regex " + fmt +func RegexError(msg string, fmt string, examples ...string) string { if len(examples) == 0 { - return s + return msg + " (regex used for validation is '" + fmt + "')" } - s += " (e.g. " + msg += " (e.g. " for i := range examples { if i > 0 { - s += " or " + msg += " or " } - s += "'" + examples[i] + "'" + msg += "'" + examples[i] + "', " } - return s + ")" + msg += "regex used for validation is '" + fmt + "')" + return msg } // EmptyError returns a string explanation of a "must not be empty" validation