Commit a5100ef0 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #17248 from thockin/airplane_validation_pt4

Auto commit by PR queue bot
parents 11635afc 0fe29232
...@@ -123,7 +123,7 @@ func validateObject(obj runtime.Object) (errors utilvalidation.ErrorList) { ...@@ -123,7 +123,7 @@ func validateObject(obj runtime.Object) (errors utilvalidation.ErrorList) {
} }
errors = expvalidation.ValidateDaemonSet(t) errors = expvalidation.ValidateDaemonSet(t)
default: default:
return utilvalidation.ErrorList{utilvalidation.NewInternalError("", fmt.Errorf("no validation defined for %#v", obj))} return utilvalidation.ErrorList{utilvalidation.NewInternalError(utilvalidation.NewFieldPath(""), fmt.Errorf("no validation defined for %#v", obj))}
} }
return errors return errors
} }
......
...@@ -92,7 +92,7 @@ func TestNewInvalid(t *testing.T) { ...@@ -92,7 +92,7 @@ func TestNewInvalid(t *testing.T) {
Details *unversioned.StatusDetails Details *unversioned.StatusDetails
}{ }{
{ {
validation.NewDuplicateError("field[0].name", "bar"), validation.NewDuplicateError(validation.NewFieldPath("field[0].name"), "bar"),
&unversioned.StatusDetails{ &unversioned.StatusDetails{
Kind: "kind", Kind: "kind",
Name: "name", Name: "name",
...@@ -103,7 +103,7 @@ func TestNewInvalid(t *testing.T) { ...@@ -103,7 +103,7 @@ func TestNewInvalid(t *testing.T) {
}, },
}, },
{ {
validation.NewInvalidError("field[0].name", "bar", "detail"), validation.NewInvalidError(validation.NewFieldPath("field[0].name"), "bar", "detail"),
&unversioned.StatusDetails{ &unversioned.StatusDetails{
Kind: "kind", Kind: "kind",
Name: "name", Name: "name",
...@@ -114,7 +114,7 @@ func TestNewInvalid(t *testing.T) { ...@@ -114,7 +114,7 @@ func TestNewInvalid(t *testing.T) {
}, },
}, },
{ {
validation.NewNotFoundError("field[0].name", "bar"), validation.NewNotFoundError(validation.NewFieldPath("field[0].name"), "bar"),
&unversioned.StatusDetails{ &unversioned.StatusDetails{
Kind: "kind", Kind: "kind",
Name: "name", Name: "name",
...@@ -125,7 +125,7 @@ func TestNewInvalid(t *testing.T) { ...@@ -125,7 +125,7 @@ func TestNewInvalid(t *testing.T) {
}, },
}, },
{ {
validation.NewNotSupportedError("field[0].name", "bar", nil), validation.NewNotSupportedError(validation.NewFieldPath("field[0].name"), "bar", nil),
&unversioned.StatusDetails{ &unversioned.StatusDetails{
Kind: "kind", Kind: "kind",
Name: "name", Name: "name",
...@@ -136,7 +136,7 @@ func TestNewInvalid(t *testing.T) { ...@@ -136,7 +136,7 @@ func TestNewInvalid(t *testing.T) {
}, },
}, },
{ {
validation.NewRequiredError("field[0].name"), validation.NewRequiredError(validation.NewFieldPath("field[0].name")),
&unversioned.StatusDetails{ &unversioned.StatusDetails{
Kind: "kind", Kind: "kind",
Name: "name", Name: "name",
......
...@@ -77,7 +77,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx api.Context, obj runtime.Obje ...@@ -77,7 +77,7 @@ func BeforeCreate(strategy RESTCreateStrategy, ctx api.Context, obj runtime.Obje
// Custom validation (including name validation) passed // Custom validation (including name validation) passed
// Now run common validation on object meta // Now run common validation on object meta
// Do this *after* custom validation so that specific error messages are shown whenever possible // Do this *after* custom validation so that specific error messages are shown whenever possible
if errs := validation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), validation.ValidatePathSegmentName); len(errs) > 0 { if errs := validation.ValidateObjectMeta(objectMeta, strategy.NamespaceScoped(), validation.ValidatePathSegmentName, utilvalidation.NewFieldPath("metadata")); len(errs) > 0 {
return errors.NewInvalid(kind, objectMeta.Name, errs) return errors.NewInvalid(kind, objectMeta.Name, errs)
} }
......
...@@ -57,13 +57,13 @@ func validateCommonFields(obj, old runtime.Object) utilvalidation.ErrorList { ...@@ -57,13 +57,13 @@ func validateCommonFields(obj, old runtime.Object) utilvalidation.ErrorList {
allErrs := utilvalidation.ErrorList{} allErrs := utilvalidation.ErrorList{}
objectMeta, err := api.ObjectMetaFor(obj) objectMeta, err := api.ObjectMetaFor(obj)
if err != nil { if err != nil {
return append(allErrs, utilvalidation.NewInternalError("metadata", err)) return append(allErrs, utilvalidation.NewInternalError(utilvalidation.NewFieldPath("metadata"), err))
} }
oldObjectMeta, err := api.ObjectMetaFor(old) oldObjectMeta, err := api.ObjectMetaFor(old)
if err != nil { if err != nil {
return append(allErrs, utilvalidation.NewInternalError("metadata", err)) return append(allErrs, utilvalidation.NewInternalError(utilvalidation.NewFieldPath("metadata"), err))
} }
allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta)...) allErrs = append(allErrs, validation.ValidateObjectMetaUpdate(objectMeta, oldObjectMeta, utilvalidation.NewFieldPath("metadata"))...)
return allErrs return allErrs
} }
......
...@@ -218,7 +218,7 @@ func TestCompatibility_v1_PodSecurityContext(t *testing.T) { ...@@ -218,7 +218,7 @@ func TestCompatibility_v1_PodSecurityContext(t *testing.T) {
} }
validator := func(obj runtime.Object) utilvalidation.ErrorList { validator := func(obj runtime.Object) utilvalidation.ErrorList {
return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec)) return validation.ValidatePodSpec(&(obj.(*api.Pod).Spec), utilvalidation.NewFieldPath("spec"))
} }
for _, tc := range cases { for _, tc := range cases {
......
...@@ -27,14 +27,14 @@ func ValidateEvent(event *api.Event) validation.ErrorList { ...@@ -27,14 +27,14 @@ func ValidateEvent(event *api.Event) validation.ErrorList {
// There is no namespace required for node. // There is no namespace required for node.
if event.InvolvedObject.Kind == "Node" && if event.InvolvedObject.Kind == "Node" &&
event.Namespace != "" { event.Namespace != "" {
allErrs = append(allErrs, validation.NewInvalidError("involvedObject.namespace", event.InvolvedObject.Namespace, "namespace is not required for node")) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "namespace is not required for node"))
} }
if event.InvolvedObject.Kind != "Node" && if event.InvolvedObject.Kind != "Node" &&
event.Namespace != event.InvolvedObject.Namespace { event.Namespace != event.InvolvedObject.Namespace {
allErrs = append(allErrs, validation.NewInvalidError("involvedObject.namespace", event.InvolvedObject.Namespace, "namespace does not match involvedObject")) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("involvedObject", "namespace"), event.InvolvedObject.Namespace, "does not match involvedObject"))
} }
if !validation.IsDNS1123Subdomain(event.Namespace) { if !validation.IsDNS1123Subdomain(event.Namespace) {
allErrs = append(allErrs, validation.NewInvalidError("namespace", event.Namespace, "")) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("namespace"), event.Namespace, ""))
} }
return allErrs return allErrs
} }
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -168,7 +168,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { ...@@ -168,7 +168,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
MaxReplicas: 5, MaxReplicas: 5,
}, },
}, },
msg: "must be bigger or equal to 1", msg: "must be greater than or equal to 1",
}, },
{ {
horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{ horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{
...@@ -184,7 +184,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { ...@@ -184,7 +184,7 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
MaxReplicas: 5, MaxReplicas: 5,
}, },
}, },
msg: "must be bigger or equal to minReplicas", msg: "must be greater than or equal to minReplicas",
}, },
{ {
horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{ horizontalPodAutoscaler: extensions.HorizontalPodAutoscaler{
...@@ -201,16 +201,16 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) { ...@@ -201,16 +201,16 @@ func TestValidateHorizontalPodAutoscaler(t *testing.T) {
CPUUtilization: &extensions.CPUTargetUtilization{TargetPercentage: -70}, CPUUtilization: &extensions.CPUTargetUtilization{TargetPercentage: -70},
}, },
}, },
msg: "must be bigger or equal to 1", msg: "must be greater than or equal to 1",
}, },
} }
for _, c := range errorCases { for _, c := range errorCases {
errs := ValidateHorizontalPodAutoscaler(&c.horizontalPodAutoscaler) errs := ValidateHorizontalPodAutoscaler(&c.horizontalPodAutoscaler)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected failure for %s", c.msg) t.Errorf("expected failure for %q", c.msg)
} else if !strings.Contains(errs[0].Error(), c.msg) { } else if !strings.Contains(errs[0].Error(), c.msg) {
t.Errorf("unexpected error: %v, expected: %s", errs[0], c.msg) t.Errorf("unexpected error: %q, expected: %q", errs[0], c.msg)
} }
} }
} }
...@@ -764,7 +764,7 @@ func TestValidateDeployment(t *testing.T) { ...@@ -764,7 +764,7 @@ func TestValidateDeployment(t *testing.T) {
Type: extensions.RecreateDeploymentStrategyType, Type: extensions.RecreateDeploymentStrategyType,
RollingUpdate: &extensions.RollingUpdateDeployment{}, RollingUpdate: &extensions.RollingUpdateDeployment{},
} }
errorCases["rollingUpdate should be nil when strategy type is Recreate"] = invalidRecreateDeployment errorCases["should be nil when strategy type is Recreate"] = invalidRecreateDeployment
// MaxSurge should be in the form of 20%. // MaxSurge should be in the form of 20%.
invalidMaxSurgeDeployment := validDeployment() invalidMaxSurgeDeployment := validDeployment()
...@@ -994,20 +994,19 @@ func TestValidateIngress(t *testing.T) { ...@@ -994,20 +994,19 @@ func TestValidateIngress(t *testing.T) {
Backend: defaultBackend, Backend: defaultBackend,
}, },
} }
badPathErr := fmt.Sprintf("spec.rules.ingressRule.http.path: invalid value '%v'", badPathErr := fmt.Sprintf("spec.rules[0].http.paths[0].path: invalid value '%v'", badPathExpr)
badPathExpr)
hostIP := "127.0.0.1" hostIP := "127.0.0.1"
badHostIP := newValid() badHostIP := newValid()
badHostIP.Spec.Rules[0].Host = hostIP badHostIP.Spec.Rules[0].Host = hostIP
badHostIPErr := fmt.Sprintf("spec.rules.host: invalid value '%v'", hostIP) badHostIPErr := fmt.Sprintf("spec.rules[0].host: invalid value '%v'", hostIP)
errorCases := map[string]extensions.Ingress{ errorCases := map[string]extensions.Ingress{
"spec.backend.serviceName: required value": servicelessBackend, "spec.backend.serviceName: required value": servicelessBackend,
"spec.backend.serviceName: invalid value": invalidNameBackend, "spec.backend.serviceName: invalid value": invalidNameBackend,
"spec.backend.servicePort: invalid value": noPortBackend, "spec.backend.servicePort: invalid value": noPortBackend,
"spec.rules.host: invalid value": badHost, "spec.rules[0].host: invalid value": badHost,
"spec.rules.ingressRule.http.paths: required value": noPaths, "spec.rules[0].http.paths: required value": noPaths,
"spec.rules.ingressRule.http.path: invalid value": noForwardSlashPath, "spec.rules[0].http.paths[0].path: invalid value": noForwardSlashPath,
} }
errorCases[badPathErr] = badRegexPath errorCases[badPathErr] = badRegexPath
errorCases[badHostIPErr] = badHostIP errorCases[badHostIPErr] = badHostIP
...@@ -1015,12 +1014,12 @@ func TestValidateIngress(t *testing.T) { ...@@ -1015,12 +1014,12 @@ func TestValidateIngress(t *testing.T) {
for k, v := range errorCases { for k, v := range errorCases {
errs := ValidateIngress(&v) errs := ValidateIngress(&v)
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %q", k)
} else { } else {
s := strings.Split(k, ":") s := strings.Split(k, ":")
err := errs[0] err := errs[0]
if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
t.Errorf("unexpected error: %v, expected: %s", err, k) t.Errorf("unexpected error: %q, expected: %q", err, k)
} }
} }
} }
...@@ -1101,8 +1100,8 @@ func TestValidateIngressStatusUpdate(t *testing.T) { ...@@ -1101,8 +1100,8 @@ func TestValidateIngressStatusUpdate(t *testing.T) {
} }
errorCases := map[string]extensions.Ingress{ errorCases := map[string]extensions.Ingress{
"status.loadBalancer.ingress.ip: invalid value": invalidIP, "status.loadBalancer.ingress[0].ip: invalid value": invalidIP,
"status.loadBalancer.ingress.hostname: invalid value": invalidHostname, "status.loadBalancer.ingress[0].hostname: invalid value": invalidHostname,
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := ValidateIngressStatusUpdate(&v, &oldValue) errs := ValidateIngressStatusUpdate(&v, &oldValue)
...@@ -1112,7 +1111,7 @@ func TestValidateIngressStatusUpdate(t *testing.T) { ...@@ -1112,7 +1111,7 @@ func TestValidateIngressStatusUpdate(t *testing.T) {
s := strings.Split(k, ":") s := strings.Split(k, ":")
err := errs[0] err := errs[0]
if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) { if err.Field != s[0] || !strings.Contains(err.Error(), s[1]) {
t.Errorf("unexpected error: %v, expected: %s", err, k) t.Errorf("unexpected error: %q, expected: %q", err, k)
} }
} }
} }
...@@ -1193,7 +1192,7 @@ func TestValidateClusterAutoscaler(t *testing.T) { ...@@ -1193,7 +1192,7 @@ func TestValidateClusterAutoscaler(t *testing.T) {
}, },
}, },
}, },
`must be bigger or equal to minNodes`: { `must be greater than or equal to minNodes`: {
ObjectMeta: api.ObjectMeta{ ObjectMeta: api.ObjectMeta{
Name: "ClusterAutoscaler", Name: "ClusterAutoscaler",
Namespace: api.NamespaceDefault, Namespace: api.NamespaceDefault,
......
...@@ -274,12 +274,12 @@ func TestCheckInvalidErr(t *testing.T) { ...@@ -274,12 +274,12 @@ func TestCheckInvalidErr(t *testing.T) {
expected string expected string
}{ }{
{ {
errors.NewInvalid("Invalid1", "invalidation", validation.ErrorList{validation.NewInvalidError("Cause", "single", "details")}), errors.NewInvalid("Invalid1", "invalidation", validation.ErrorList{validation.NewInvalidError(validation.NewFieldPath("field"), "single", "details")}),
`Error from server: Invalid1 "invalidation" is invalid: Cause: invalid value 'single', Details: details`, `Error from server: Invalid1 "invalidation" is invalid: field: invalid value 'single', Details: details`,
}, },
{ {
errors.NewInvalid("Invalid2", "invalidation", validation.ErrorList{validation.NewInvalidError("Cause", "multi1", "details"), validation.NewInvalidError("Cause", "multi2", "details")}), errors.NewInvalid("Invalid2", "invalidation", validation.ErrorList{validation.NewInvalidError(validation.NewFieldPath("field1"), "multi1", "details"), validation.NewInvalidError(validation.NewFieldPath("field2"), "multi2", "details")}),
`Error from server: Invalid2 "invalidation" is invalid: [Cause: invalid value 'multi1', Details: details, Cause: invalid value 'multi2', Details: details]`, `Error from server: Invalid2 "invalidation" is invalid: [field1: invalid value 'multi1', Details: details, field2: invalid value 'multi2', Details: details]`,
}, },
{ {
errors.NewInvalid("Invalid3", "invalidation", validation.ErrorList{}), errors.NewInvalid("Invalid3", "invalidation", validation.ErrorList{}),
......
...@@ -317,7 +317,8 @@ func filterInvalidPods(pods []*api.Pod, source string, recorder record.EventReco ...@@ -317,7 +317,8 @@ func filterInvalidPods(pods []*api.Pod, source string, recorder record.EventReco
} else { } else {
name := kubecontainer.GetPodFullName(pod) name := kubecontainer.GetPodFullName(pod)
if names.Has(name) { if names.Has(name) {
errlist = append(errlist, utilvalidation.NewDuplicateError("name", pod.Name)) //FIXME: this implies an API version
errlist = append(errlist, utilvalidation.NewDuplicateError(utilvalidation.NewFieldPath("metadata", "name"), pod.Name))
} else { } else {
names.Insert(name) names.Insert(name)
} }
......
...@@ -77,6 +77,7 @@ import ( ...@@ -77,6 +77,7 @@ import (
"k8s.io/kubernetes/pkg/util/procfs" "k8s.io/kubernetes/pkg/util/procfs"
"k8s.io/kubernetes/pkg/util/selinux" "k8s.io/kubernetes/pkg/util/selinux"
"k8s.io/kubernetes/pkg/util/sets" "k8s.io/kubernetes/pkg/util/sets"
utilvalidation "k8s.io/kubernetes/pkg/util/validation"
"k8s.io/kubernetes/pkg/util/yaml" "k8s.io/kubernetes/pkg/util/yaml"
"k8s.io/kubernetes/pkg/version" "k8s.io/kubernetes/pkg/version"
"k8s.io/kubernetes/pkg/volume" "k8s.io/kubernetes/pkg/volume"
...@@ -2163,7 +2164,7 @@ func (s podsByCreationTime) Less(i, j int) bool { ...@@ -2163,7 +2164,7 @@ func (s podsByCreationTime) Less(i, j int) bool {
func hasHostPortConflicts(pods []*api.Pod) bool { func hasHostPortConflicts(pods []*api.Pod) bool {
ports := sets.String{} ports := sets.String{}
for _, pod := range pods { for _, pod := range pods {
if errs := validation.AccumulateUniqueHostPorts(pod.Spec.Containers, &ports); len(errs) > 0 { if errs := validation.AccumulateUniqueHostPorts(pod.Spec.Containers, &ports, utilvalidation.NewFieldPath("spec", "containers")); len(errs) > 0 {
glog.Errorf("Pod %q: HostPort is already allocated, ignoring: %v", kubecontainer.GetPodFullName(pod), errs) glog.Errorf("Pod %q: HostPort is already allocated, ignoring: %v", kubecontainer.GetPodFullName(pod), errs)
return true return true
} }
......
...@@ -701,14 +701,15 @@ const qualifiedNameErrorMsg string = "must match regex [" + validation.DNS1123Su ...@@ -701,14 +701,15 @@ const qualifiedNameErrorMsg string = "must match regex [" + validation.DNS1123Su
func validateLabelKey(k string) error { func validateLabelKey(k string) error {
if !validation.IsQualifiedName(k) { if !validation.IsQualifiedName(k) {
return validation.NewInvalidError("label key", k, qualifiedNameErrorMsg) return fmt.Errorf("invalid label key: %s", qualifiedNameErrorMsg)
} }
return nil return nil
} }
func validateLabelValue(v string) error { func validateLabelValue(v string) error {
if !validation.IsValidLabelValue(v) { if !validation.IsValidLabelValue(v) {
return validation.NewInvalidError("label value", v, qualifiedNameErrorMsg) //FIXME: this is not the right regex!
return fmt.Errorf("invalid label value: %s", qualifiedNameErrorMsg)
} }
return nil return nil
} }
......
...@@ -134,10 +134,10 @@ func (r *BindingREST) Create(ctx api.Context, obj runtime.Object) (out runtime.O ...@@ -134,10 +134,10 @@ func (r *BindingREST) Create(ctx api.Context, obj runtime.Object) (out runtime.O
binding := obj.(*api.Binding) binding := obj.(*api.Binding)
// TODO: move me to a binding strategy // TODO: move me to a binding strategy
if len(binding.Target.Kind) != 0 && binding.Target.Kind != "Node" { if len(binding.Target.Kind) != 0 && binding.Target.Kind != "Node" {
return nil, errors.NewInvalid("binding", binding.Name, validation.ErrorList{validation.NewInvalidError("to.kind", binding.Target.Kind, "must be empty or 'Node'")}) return nil, errors.NewInvalid("binding", binding.Name, validation.ErrorList{validation.NewInvalidError(validation.NewFieldPath("target", "kind"), binding.Target.Kind, "must be empty or 'Node'")})
} }
if len(binding.Target.Name) == 0 { if len(binding.Target.Name) == 0 {
return nil, errors.NewInvalid("binding", binding.Name, validation.ErrorList{validation.NewRequiredError("to.name")}) return nil, errors.NewInvalid("binding", binding.Name, validation.ErrorList{validation.NewRequiredError(validation.NewFieldPath("target", "name"))})
} }
err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations) err = r.assignPod(ctx, binding.Name, binding.Target.Name, binding.Annotations)
out = &unversioned.Status{Status: unversioned.StatusSuccess} out = &unversioned.Status{Status: unversioned.StatusSuccess}
......
...@@ -84,7 +84,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, err ...@@ -84,7 +84,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, err
// Allocate next available. // Allocate next available.
ip, err := rs.serviceIPs.AllocateNext() ip, err := rs.serviceIPs.AllocateNext()
if err != nil { if err != nil {
el := utilvalidation.ErrorList{utilvalidation.NewInvalidError("spec.clusterIP", service.Spec.ClusterIP, err.Error())} el := utilvalidation.ErrorList{utilvalidation.NewInvalidError(utilvalidation.NewFieldPath("spec", "clusterIP"), service.Spec.ClusterIP, err.Error())}
return nil, errors.NewInvalid("Service", service.Name, el) return nil, errors.NewInvalid("Service", service.Name, el)
} }
service.Spec.ClusterIP = ip.String() service.Spec.ClusterIP = ip.String()
...@@ -92,7 +92,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, err ...@@ -92,7 +92,7 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, err
} else if api.IsServiceIPSet(service) { } else if api.IsServiceIPSet(service) {
// Try to respect the requested IP. // Try to respect the requested IP.
if err := rs.serviceIPs.Allocate(net.ParseIP(service.Spec.ClusterIP)); err != nil { if err := rs.serviceIPs.Allocate(net.ParseIP(service.Spec.ClusterIP)); err != nil {
el := utilvalidation.ErrorList{utilvalidation.NewInvalidError("spec.clusterIP", service.Spec.ClusterIP, err.Error())} el := utilvalidation.ErrorList{utilvalidation.NewInvalidError(utilvalidation.NewFieldPath("spec", "clusterIP"), service.Spec.ClusterIP, err.Error())}
return nil, errors.NewInvalid("Service", service.Name, el) return nil, errors.NewInvalid("Service", service.Name, el)
} }
releaseServiceIP = true releaseServiceIP = true
...@@ -104,13 +104,13 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, err ...@@ -104,13 +104,13 @@ func (rs *REST) Create(ctx api.Context, obj runtime.Object) (runtime.Object, err
if servicePort.NodePort != 0 { if servicePort.NodePort != 0 {
err := nodePortOp.Allocate(servicePort.NodePort) err := nodePortOp.Allocate(servicePort.NodePort)
if err != nil { if err != nil {
el := utilvalidation.ErrorList{utilvalidation.NewInvalidError("nodePort", servicePort.NodePort, err.Error())}.PrefixIndex(i).Prefix("spec.ports") el := utilvalidation.ErrorList{utilvalidation.NewInvalidError(utilvalidation.NewFieldPath("spec", "ports").Index(i).Child("nodePort"), servicePort.NodePort, err.Error())}
return nil, errors.NewInvalid("Service", service.Name, el) return nil, errors.NewInvalid("Service", service.Name, el)
} }
} else if assignNodePorts { } else if assignNodePorts {
nodePort, err := nodePortOp.AllocateNext() nodePort, err := nodePortOp.AllocateNext()
if err != nil { if err != nil {
el := utilvalidation.ErrorList{utilvalidation.NewInvalidError("nodePort", servicePort.NodePort, err.Error())}.PrefixIndex(i).Prefix("spec.ports") el := utilvalidation.ErrorList{utilvalidation.NewInvalidError(utilvalidation.NewFieldPath("spec", "ports").Index(i).Child("nodePort"), servicePort.NodePort, err.Error())}
return nil, errors.NewInvalid("Service", service.Name, el) return nil, errors.NewInvalid("Service", service.Name, el)
} }
servicePort.NodePort = nodePort servicePort.NodePort = nodePort
...@@ -223,14 +223,14 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, boo ...@@ -223,14 +223,14 @@ func (rs *REST) Update(ctx api.Context, obj runtime.Object) (runtime.Object, boo
if !contains(oldNodePorts, nodePort) { if !contains(oldNodePorts, nodePort) {
err := nodePortOp.Allocate(nodePort) err := nodePortOp.Allocate(nodePort)
if err != nil { if err != nil {
el := utilvalidation.ErrorList{utilvalidation.NewInvalidError("nodePort", nodePort, err.Error())}.PrefixIndex(i).Prefix("spec.ports") el := utilvalidation.ErrorList{utilvalidation.NewInvalidError(utilvalidation.NewFieldPath("spec", "ports").Index(i).Child("nodePort"), nodePort, err.Error())}
return nil, false, errors.NewInvalid("Service", service.Name, el) return nil, false, errors.NewInvalid("Service", service.Name, el)
} }
} }
} else { } else {
nodePort, err = nodePortOp.AllocateNext() nodePort, err = nodePortOp.AllocateNext()
if err != nil { if err != nil {
el := utilvalidation.ErrorList{utilvalidation.NewInvalidError("nodePort", nodePort, err.Error())}.PrefixIndex(i).Prefix("spec.ports") el := utilvalidation.ErrorList{utilvalidation.NewInvalidError(utilvalidation.NewFieldPath("spec", "ports").Index(i).Child("nodePort"), nodePort, err.Error())}
return nil, false, errors.NewInvalid("Service", service.Name, el) return nil, false, errors.NewInvalid("Service", service.Name, el)
} }
servicePort.NodePort = nodePort servicePort.NodePort = nodePort
......
...@@ -47,8 +47,11 @@ func ParseWatchResourceVersion(resourceVersion, kind string) (uint64, error) { ...@@ -47,8 +47,11 @@ func ParseWatchResourceVersion(resourceVersion, kind string) (uint64, error) {
} }
version, err := strconv.ParseUint(resourceVersion, 10, 64) version, err := strconv.ParseUint(resourceVersion, 10, 64)
if err != nil { if err != nil {
// TODO: Does this need to be a ErrorList? I can't convince myself it does. return 0, errors.NewInvalid(kind, "", utilvalidation.ErrorList{
return 0, errors.NewInvalid(kind, "", utilvalidation.ErrorList{utilvalidation.NewInvalidError("resourceVersion", resourceVersion, err.Error())}) // Validation errors are supposed to return version-specific field
// paths, but this is probably close enough.
utilvalidation.NewInvalidError(utilvalidation.NewFieldPath("resourceVersion"), resourceVersion, err.Error()),
})
} }
return version + 1, nil return version + 1, nil
} }
......
...@@ -121,88 +121,66 @@ func (t ErrorType) String() string { ...@@ -121,88 +121,66 @@ func (t ErrorType) String() string {
// NewNotFoundError returns a *Error indicating "value not found". This is // NewNotFoundError returns a *Error indicating "value not found". This is
// used to report failure to find a requested value (e.g. looking up an ID). // used to report failure to find a requested value (e.g. looking up an ID).
func NewNotFoundError(field string, value interface{}) *Error { func NewNotFoundError(field *FieldPath, value interface{}) *Error {
return &Error{ErrorTypeNotFound, field, value, ""} return &Error{ErrorTypeNotFound, field.String(), value, ""}
} }
// NewRequiredError returns a *Error indicating "value required". This is used // NewRequiredError returns a *Error indicating "value required". This is used
// to report required values that are not provided (e.g. empty strings, null // to report required values that are not provided (e.g. empty strings, null
// values, or empty arrays). // values, or empty arrays).
func NewRequiredError(field string) *Error { func NewRequiredError(field *FieldPath) *Error {
return &Error{ErrorTypeRequired, field, "", ""} return &Error{ErrorTypeRequired, field.String(), "", ""}
} }
// NewDuplicateError returns a *Error indicating "duplicate value". This is // NewDuplicateError returns a *Error indicating "duplicate value". This is
// used to report collisions of values that must be unique (e.g. names or IDs). // used to report collisions of values that must be unique (e.g. names or IDs).
func NewDuplicateError(field string, value interface{}) *Error { func NewDuplicateError(field *FieldPath, value interface{}) *Error {
return &Error{ErrorTypeDuplicate, field, value, ""} return &Error{ErrorTypeDuplicate, field.String(), value, ""}
} }
// NewInvalidError returns a *Error indicating "invalid value". This is used // NewInvalidError returns a *Error indicating "invalid value". This is used
// to report malformed values (e.g. failed regex match, too long, out of bounds). // to report malformed values (e.g. failed regex match, too long, out of bounds).
func NewInvalidError(field string, value interface{}, detail string) *Error { func NewInvalidError(field *FieldPath, value interface{}, detail string) *Error {
return &Error{ErrorTypeInvalid, field, value, detail} return &Error{ErrorTypeInvalid, field.String(), value, detail}
} }
// NewNotSupportedError returns a *Error indicating "unsupported value". // NewNotSupportedError returns a *Error indicating "unsupported value".
// This is used to report unknown values for enumerated fields (e.g. a list of // This is used to report unknown values for enumerated fields (e.g. a list of
// valid values). // valid values).
func NewNotSupportedError(field string, value interface{}, validValues []string) *Error { func NewNotSupportedError(field *FieldPath, value interface{}, validValues []string) *Error {
detail := "" detail := ""
if validValues != nil && len(validValues) > 0 { if validValues != nil && len(validValues) > 0 {
detail = "supported values: " + strings.Join(validValues, ", ") detail = "supported values: " + strings.Join(validValues, ", ")
} }
return &Error{ErrorTypeNotSupported, field, value, detail} return &Error{ErrorTypeNotSupported, field.String(), value, detail}
} }
// NewForbiddenError returns a *Error indicating "forbidden". This is used to // NewForbiddenError returns a *Error indicating "forbidden". This is used to
// report valid (as per formatting rules) values which would be accepted under // report valid (as per formatting rules) values which would be accepted under
// some conditions, but which are not permitted by current conditions (e.g. // some conditions, but which are not permitted by current conditions (e.g.
// security policy). // security policy).
func NewForbiddenError(field string, value interface{}) *Error { func NewForbiddenError(field *FieldPath, value interface{}) *Error {
return &Error{ErrorTypeForbidden, field, value, ""} return &Error{ErrorTypeForbidden, field.String(), value, ""}
} }
// NewTooLongError returns a *Error indicating "too long". This is used to // NewTooLongError returns a *Error indicating "too long". This is used to
// report that the given value is too long. This is similar to // report that the given value is too long. This is similar to
// NewInvalidError, but the returned error will not include the too-long // NewInvalidError, but the returned error will not include the too-long
// value. // value.
func NewTooLongError(field string, value interface{}, maxLength int) *Error { func NewTooLongError(field *FieldPath, value interface{}, maxLength int) *Error {
return &Error{ErrorTypeTooLong, field, value, fmt.Sprintf("must have at most %d characters", maxLength)} return &Error{ErrorTypeTooLong, field.String(), value, fmt.Sprintf("must have at most %d characters", maxLength)}
} }
// NewInternalError returns a *Error indicating "internal error". This is used // NewInternalError returns a *Error indicating "internal error". This is used
// to signal that an error was found that was not directly related to user // to signal that an error was found that was not directly related to user
// input. The err argument must be non-nil. // input. The err argument must be non-nil.
func NewInternalError(field string, err error) *Error { func NewInternalError(field *FieldPath, err error) *Error {
return &Error{ErrorTypeInternal, field, nil, err.Error()} return &Error{ErrorTypeInternal, field.String(), nil, err.Error()}
} }
// ErrorList holds a set of errors. // ErrorList holds a set of errors.
type ErrorList []*Error type ErrorList []*Error
// Prefix adds a prefix to the Field of every Error in the list.
// Returns the list for convenience.
func (list ErrorList) Prefix(prefix string) ErrorList {
for i := range list {
err := list[i]
if strings.HasPrefix(err.Field, "[") {
err.Field = prefix + err.Field
} else if len(err.Field) != 0 {
err.Field = prefix + "." + err.Field
} else {
err.Field = prefix
}
}
return list
}
// PrefixIndex adds an index to the Field of every Error in the list.
// Returns the list for convenience.
func (list ErrorList) PrefixIndex(index int) ErrorList {
return list.Prefix(fmt.Sprintf("[%d]", index))
}
// NewErrorTypeMatcher returns an errors.Matcher that returns true // NewErrorTypeMatcher returns an errors.Matcher that returns true
// if the provided error is a Error and has the provided ErrorType. // if the provided error is a Error and has the provided ErrorType.
func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher { func NewErrorTypeMatcher(t ErrorType) utilerrors.Matcher {
......
...@@ -28,27 +28,27 @@ func TestMakeFuncs(t *testing.T) { ...@@ -28,27 +28,27 @@ func TestMakeFuncs(t *testing.T) {
expected ErrorType expected ErrorType
}{ }{
{ {
func() *Error { return NewInvalidError("f", "v", "d") }, func() *Error { return NewInvalidError(NewFieldPath("f"), "v", "d") },
ErrorTypeInvalid, ErrorTypeInvalid,
}, },
{ {
func() *Error { return NewNotSupportedError("f", "v", nil) }, func() *Error { return NewNotSupportedError(NewFieldPath("f"), "v", nil) },
ErrorTypeNotSupported, ErrorTypeNotSupported,
}, },
{ {
func() *Error { return NewDuplicateError("f", "v") }, func() *Error { return NewDuplicateError(NewFieldPath("f"), "v") },
ErrorTypeDuplicate, ErrorTypeDuplicate,
}, },
{ {
func() *Error { return NewNotFoundError("f", "v") }, func() *Error { return NewNotFoundError(NewFieldPath("f"), "v") },
ErrorTypeNotFound, ErrorTypeNotFound,
}, },
{ {
func() *Error { return NewRequiredError("f") }, func() *Error { return NewRequiredError(NewFieldPath("f")) },
ErrorTypeRequired, ErrorTypeRequired,
}, },
{ {
func() *Error { return NewInternalError("f", fmt.Errorf("e")) }, func() *Error { return NewInternalError(NewFieldPath("f"), fmt.Errorf("e")) },
ErrorTypeInternal, ErrorTypeInternal,
}, },
} }
...@@ -62,7 +62,7 @@ func TestMakeFuncs(t *testing.T) { ...@@ -62,7 +62,7 @@ func TestMakeFuncs(t *testing.T) {
} }
func TestErrorUsefulMessage(t *testing.T) { func TestErrorUsefulMessage(t *testing.T) {
s := NewInvalidError("foo", "bar", "deet").Error() s := NewInvalidError(NewFieldPath("foo"), "bar", "deet").Error()
t.Logf("message: %v", s) t.Logf("message: %v", s)
for _, part := range []string{"foo", "bar", "deet", ErrorTypeInvalid.String()} { for _, part := range []string{"foo", "bar", "deet", ErrorTypeInvalid.String()} {
if !strings.Contains(s, part) { if !strings.Contains(s, part) {
...@@ -77,7 +77,7 @@ func TestErrorUsefulMessage(t *testing.T) { ...@@ -77,7 +77,7 @@ func TestErrorUsefulMessage(t *testing.T) {
KV map[string]int KV map[string]int
} }
s = NewInvalidError( s = NewInvalidError(
"foo", NewFieldPath("foo"),
&complicated{ &complicated{
Baz: 1, Baz: 1,
Qux: "aoeu", Qux: "aoeu",
...@@ -102,8 +102,8 @@ func TestToAggregate(t *testing.T) { ...@@ -102,8 +102,8 @@ func TestToAggregate(t *testing.T) {
testCases := []ErrorList{ testCases := []ErrorList{
nil, nil,
{}, {},
{NewInvalidError("f", "v", "d")}, {NewInvalidError(NewFieldPath("f"), "v", "d")},
{NewInvalidError("f", "v", "d"), NewInternalError("", fmt.Errorf("e"))}, {NewInvalidError(NewFieldPath("f"), "v", "d"), NewInternalError(NewFieldPath(""), fmt.Errorf("e"))},
} }
for i, tc := range testCases { for i, tc := range testCases {
agg := tc.ToAggregate() agg := tc.ToAggregate()
...@@ -121,9 +121,9 @@ func TestToAggregate(t *testing.T) { ...@@ -121,9 +121,9 @@ func TestToAggregate(t *testing.T) {
func TestErrListFilter(t *testing.T) { func TestErrListFilter(t *testing.T) {
list := ErrorList{ list := ErrorList{
NewInvalidError("test.field", "", ""), NewInvalidError(NewFieldPath("test.field"), "", ""),
NewInvalidError("field.test", "", ""), NewInvalidError(NewFieldPath("field.test"), "", ""),
NewDuplicateError("test", "value"), NewDuplicateError(NewFieldPath("test"), "value"),
} }
if len(list.Filter(NewErrorTypeMatcher(ErrorTypeDuplicate))) != 2 { if len(list.Filter(NewErrorTypeMatcher(ErrorTypeDuplicate))) != 2 {
t.Errorf("should not filter") t.Errorf("should not filter")
...@@ -132,63 +132,3 @@ func TestErrListFilter(t *testing.T) { ...@@ -132,63 +132,3 @@ func TestErrListFilter(t *testing.T) {
t.Errorf("should filter") t.Errorf("should filter")
} }
} }
func TestErrListPrefix(t *testing.T) {
testCases := []struct {
Err *Error
Expected string
}{
{
NewNotFoundError("[0].bar", "value"),
"foo[0].bar",
},
{
NewInvalidError("field", "value", ""),
"foo.field",
},
{
NewDuplicateError("", "value"),
"foo",
},
}
for _, testCase := range testCases {
errList := ErrorList{testCase.Err}
prefix := errList.Prefix("foo")
if prefix == nil || len(prefix) != len(errList) {
t.Errorf("Prefix should return self")
}
if e, a := testCase.Expected, errList[0].Field; e != a {
t.Errorf("expected %s, got %s", e, a)
}
}
}
func TestErrListPrefixIndex(t *testing.T) {
testCases := []struct {
Err *Error
Expected string
}{
{
NewNotFoundError("[0].bar", "value"),
"[1][0].bar",
},
{
NewInvalidError("field", "value", ""),
"[1].field",
},
{
NewDuplicateError("", "value"),
"[1]",
},
}
for _, testCase := range testCases {
errList := ErrorList{testCase.Err}
prefix := errList.PrefixIndex(1)
if prefix == nil || len(prefix) != len(errList) {
t.Errorf("PrefixIndex should return self")
}
if e, a := testCase.Expected, errList[0].Field; e != a {
t.Errorf("expected %s, got %s", e, a)
}
}
}
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import (
"bytes"
"fmt"
"strconv"
)
// FieldPath represents the path from some root to a particular field.
type FieldPath struct {
name string // the name of this field or "" if this is an index
index string // if name == "", this is a subscript (index or map key) of the previous element
parent *FieldPath // nil if this is the root element
}
// NewFieldPath creates a root FieldPath object.
func NewFieldPath(name string, moreNames ...string) *FieldPath {
r := &FieldPath{name: name, parent: nil}
for _, anotherName := range moreNames {
r = &FieldPath{name: anotherName, parent: r}
}
return r
}
// Root returns the root element of this FieldPath.
func (fp *FieldPath) Root() *FieldPath {
for ; fp.parent != nil; fp = fp.parent {
// Do nothing.
}
return fp
}
// Child creates a new FieldPath that is a child of the method receiver.
func (fp *FieldPath) Child(name string, moreNames ...string) *FieldPath {
r := NewFieldPath(name, moreNames...)
r.Root().parent = fp
return r
}
// Index indicates that the previous FieldPath is to be subscripted by an int.
// This sets the same underlying value as Key.
func (fp *FieldPath) Index(index int) *FieldPath {
return &FieldPath{index: strconv.Itoa(index), parent: fp}
}
// Key indicates that the previous FieldPath is to be subscripted by a string.
// This sets the same underlying value as Index.
func (fp *FieldPath) Key(key string) *FieldPath {
return &FieldPath{index: key, parent: fp}
}
// String produces a string representation of the FieldPath.
func (fp *FieldPath) String() string {
// make a slice to iterate
elems := []*FieldPath{}
for p := fp; p != nil; p = p.parent {
elems = append(elems, p)
}
// iterate, but it has to be backwards
buf := bytes.NewBuffer(nil)
for i := range elems {
p := elems[len(elems)-1-i]
if p.parent != nil && len(p.name) > 0 {
// This is either the root or it is a subscript.
buf.WriteString(".")
}
if len(p.name) > 0 {
buf.WriteString(p.name)
} else {
fmt.Fprintf(buf, "[%s]", p.index)
}
}
return buf.String()
}
/*
Copyright 2015 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package validation
import "testing"
func TestFieldPath(t *testing.T) {
testCases := []struct {
op func(*FieldPath) *FieldPath
expected string
}{
{
func(fp *FieldPath) *FieldPath { return fp },
"root",
},
{
func(fp *FieldPath) *FieldPath { return fp.Child("first") },
"root.first",
},
{
func(fp *FieldPath) *FieldPath { return fp.Child("second") },
"root.first.second",
},
{
func(fp *FieldPath) *FieldPath { return fp.Index(0) },
"root.first.second[0]",
},
{
func(fp *FieldPath) *FieldPath { return fp.Child("third") },
"root.first.second[0].third",
},
{
func(fp *FieldPath) *FieldPath { return fp.Index(93) },
"root.first.second[0].third[93]",
},
{
func(fp *FieldPath) *FieldPath { return fp.parent },
"root.first.second[0].third",
},
{
func(fp *FieldPath) *FieldPath { return fp.parent },
"root.first.second[0]",
},
{
func(fp *FieldPath) *FieldPath { return fp.Key("key") },
"root.first.second[0][key]",
},
}
root := NewFieldPath("root")
fp := root
for i, tc := range testCases {
fp = tc.op(fp)
if fp.String() != tc.expected {
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, fp.String())
}
if fp.Root() != root {
t.Errorf("[%d] Wrong root: %#v", i, fp.Root())
}
}
}
func TestFieldPathMultiArg(t *testing.T) {
testCases := []struct {
op func(*FieldPath) *FieldPath
expected string
}{
{
func(fp *FieldPath) *FieldPath { return fp },
"root.first",
},
{
func(fp *FieldPath) *FieldPath { return fp.Child("second", "third") },
"root.first.second.third",
},
{
func(fp *FieldPath) *FieldPath { return fp.Index(0) },
"root.first.second.third[0]",
},
{
func(fp *FieldPath) *FieldPath { return fp.parent },
"root.first.second.third",
},
{
func(fp *FieldPath) *FieldPath { return fp.parent },
"root.first.second",
},
{
func(fp *FieldPath) *FieldPath { return fp.parent },
"root.first",
},
{
func(fp *FieldPath) *FieldPath { return fp.parent },
"root",
},
}
root := NewFieldPath("root", "first")
fp := root
for i, tc := range testCases {
fp = tc.op(fp)
if fp.String() != tc.expected {
t.Errorf("[%d] Expected %q, got %q", i, tc.expected, fp.String())
}
if fp.Root() != root.Root() {
t.Errorf("[%d] Wrong root: %#v", i, fp.Root())
}
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment