Convert validation to use FieldPath

Before this change we have a mish-mash of ways to pass field names around for
error generation.  Sometimes string fieldnames, sometimes .Prefix(), sometimes
neither, often wrong names or not indexed when it should be.

Instead of that mess, this is part one of a couple of commits that will make it
more strongly typed and hopefully encourage correct behavior.  At least you
will have to think about field names, which is better than nothing.

It turned out to be really hard to do this incrementally.
This commit is contained in:
Tim Hockin 2015-11-04 13:52:14 -08:00
parent 102eced0b1
commit e6df0b1a24
19 changed files with 986 additions and 807 deletions

View File

@ -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
} }

View File

@ -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) {
}, },
}, },
{ {
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) {
}, },
}, },
{ {
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) {
}, },
}, },
{ {
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) {
}, },
}, },
{ {
validation.NewRequiredError("field[0].name"), validation.NewRequiredError(validation.NewFieldPath("field[0].name")),
&unversioned.StatusDetails{ &unversioned.StatusDetails{
Kind: "kind", Kind: "kind",
Name: "name", Name: "name",

View File

@ -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)
} }

View File

@ -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
} }

View File

@ -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 {

View File

@ -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
} }

File diff suppressed because it is too large Load Diff

View File

@ -43,12 +43,16 @@ func expectPrefix(t *testing.T, prefix string, errs validation.ErrorList) {
// Ensure custom name functions are allowed // Ensure custom name functions are allowed
func TestValidateObjectMetaCustomName(t *testing.T) { func TestValidateObjectMetaCustomName(t *testing.T) {
errs := ValidateObjectMeta(&api.ObjectMeta{Name: "test", GenerateName: "foo"}, false, func(s string, prefix bool) (bool, string) { errs := ValidateObjectMeta(
if s == "test" { &api.ObjectMeta{Name: "test", GenerateName: "foo"},
return true, "" false,
} func(s string, prefix bool) (bool, string) {
return false, "name-gen" if s == "test" {
}) return true, ""
}
return false, "name-gen"
},
validation.NewFieldPath("field"))
if len(errs) != 1 { if len(errs) != 1 {
t.Fatalf("unexpected errors: %v", errs) t.Fatalf("unexpected errors: %v", errs)
} }
@ -59,9 +63,13 @@ func TestValidateObjectMetaCustomName(t *testing.T) {
// Ensure namespace names follow dns label format // Ensure namespace names follow dns label format
func TestValidateObjectMetaNamespaces(t *testing.T) { func TestValidateObjectMetaNamespaces(t *testing.T) {
errs := ValidateObjectMeta(&api.ObjectMeta{Name: "test", Namespace: "foo.bar"}, false, func(s string, prefix bool) (bool, string) { errs := ValidateObjectMeta(
return true, "" &api.ObjectMeta{Name: "test", Namespace: "foo.bar"},
}) false,
func(s string, prefix bool) (bool, string) {
return true, ""
},
validation.NewFieldPath("field"))
if len(errs) != 1 { if len(errs) != 1 {
t.Fatalf("unexpected errors: %v", errs) t.Fatalf("unexpected errors: %v", errs)
} }
@ -74,9 +82,13 @@ func TestValidateObjectMetaNamespaces(t *testing.T) {
for i := range b { for i := range b {
b[i] = letters[rand.Intn(len(letters))] b[i] = letters[rand.Intn(len(letters))]
} }
errs = ValidateObjectMeta(&api.ObjectMeta{Name: "test", Namespace: string(b)}, false, func(s string, prefix bool) (bool, string) { errs = ValidateObjectMeta(
return true, "" &api.ObjectMeta{Name: "test", Namespace: string(b)},
}) false,
func(s string, prefix bool) (bool, string) {
return true, ""
},
validation.NewFieldPath("field"))
if len(errs) != 1 { if len(errs) != 1 {
t.Fatalf("unexpected errors: %v", errs) t.Fatalf("unexpected errors: %v", errs)
} }
@ -89,18 +101,21 @@ func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) {
if errs := ValidateObjectMetaUpdate( if errs := ValidateObjectMetaUpdate(
&api.ObjectMeta{Name: "test", ResourceVersion: "1"}, &api.ObjectMeta{Name: "test", ResourceVersion: "1"},
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(10, 0))}, &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(10, 0))},
validation.NewFieldPath("field"),
); len(errs) != 0 { ); len(errs) != 0 {
t.Fatalf("unexpected errors: %v", errs) t.Fatalf("unexpected errors: %v", errs)
} }
if errs := ValidateObjectMetaUpdate( if errs := ValidateObjectMetaUpdate(
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(10, 0))}, &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(10, 0))},
&api.ObjectMeta{Name: "test", ResourceVersion: "1"}, &api.ObjectMeta{Name: "test", ResourceVersion: "1"},
validation.NewFieldPath("field"),
); len(errs) != 0 { ); len(errs) != 0 {
t.Fatalf("unexpected errors: %v", errs) t.Fatalf("unexpected errors: %v", errs)
} }
if errs := ValidateObjectMetaUpdate( if errs := ValidateObjectMetaUpdate(
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(10, 0))}, &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(10, 0))},
&api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(11, 0))}, &api.ObjectMeta{Name: "test", ResourceVersion: "1", CreationTimestamp: unversioned.NewTime(time.Unix(11, 0))},
validation.NewFieldPath("field"),
); len(errs) != 0 { ); len(errs) != 0 {
t.Fatalf("unexpected errors: %v", errs) t.Fatalf("unexpected errors: %v", errs)
} }
@ -108,7 +123,11 @@ func TestValidateObjectMetaUpdateIgnoresCreationTimestamp(t *testing.T) {
// Ensure trailing slash is allowed in generate name // Ensure trailing slash is allowed in generate name
func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) { func TestValidateObjectMetaTrimsTrailingSlash(t *testing.T) {
errs := ValidateObjectMeta(&api.ObjectMeta{Name: "test", GenerateName: "foo-"}, false, NameIsDNSSubdomain) errs := ValidateObjectMeta(
&api.ObjectMeta{Name: "test", GenerateName: "foo-"},
false,
NameIsDNSSubdomain,
validation.NewFieldPath("field"))
if len(errs) != 0 { if len(errs) != 0 {
t.Fatalf("unexpected errors: %v", errs) t.Fatalf("unexpected errors: %v", errs)
} }
@ -132,7 +151,7 @@ func TestValidateLabels(t *testing.T) {
{"goodvalue": "123_-.BaR"}, {"goodvalue": "123_-.BaR"},
} }
for i := range successCases { for i := range successCases {
errs := ValidateLabels(successCases[i], "field") errs := ValidateLabels(successCases[i], validation.NewFieldPath("field"))
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("case[%d] expected success, got %#v", i, errs) t.Errorf("case[%d] expected success, got %#v", i, errs)
} }
@ -145,7 +164,7 @@ func TestValidateLabels(t *testing.T) {
{strings.Repeat("a", 254): "bar"}, {strings.Repeat("a", 254): "bar"},
} }
for i := range labelNameErrorCases { for i := range labelNameErrorCases {
errs := ValidateLabels(labelNameErrorCases[i], "field") errs := ValidateLabels(labelNameErrorCases[i], validation.NewFieldPath("field"))
if len(errs) != 1 { if len(errs) != 1 {
t.Errorf("case[%d] expected failure", i) t.Errorf("case[%d] expected failure", i)
} else { } else {
@ -163,7 +182,7 @@ func TestValidateLabels(t *testing.T) {
{"strangecharsinvalue": "?#$notsogood"}, {"strangecharsinvalue": "?#$notsogood"},
} }
for i := range labelValueErrorCases { for i := range labelValueErrorCases {
errs := ValidateLabels(labelValueErrorCases[i], "field") errs := ValidateLabels(labelValueErrorCases[i], validation.NewFieldPath("field"))
if len(errs) != 1 { if len(errs) != 1 {
t.Errorf("case[%d] expected failure", i) t.Errorf("case[%d] expected failure", i)
} else { } else {
@ -197,7 +216,7 @@ func TestValidateAnnotations(t *testing.T) {
}, },
} }
for i := range successCases { for i := range successCases {
errs := ValidateAnnotations(successCases[i], "field") errs := ValidateAnnotations(successCases[i], validation.NewFieldPath("field"))
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("case[%d] expected success, got %#v", i, errs) t.Errorf("case[%d] expected success, got %#v", i, errs)
} }
@ -210,7 +229,7 @@ func TestValidateAnnotations(t *testing.T) {
{strings.Repeat("a", 254): "bar"}, {strings.Repeat("a", 254): "bar"},
} }
for i := range nameErrorCases { for i := range nameErrorCases {
errs := ValidateAnnotations(nameErrorCases[i], "field") errs := ValidateAnnotations(nameErrorCases[i], validation.NewFieldPath("field"))
if len(errs) != 1 { if len(errs) != 1 {
t.Errorf("case[%d] expected failure", i) t.Errorf("case[%d] expected failure", i)
} }
@ -227,7 +246,7 @@ func TestValidateAnnotations(t *testing.T) {
}, },
} }
for i := range totalSizeErrorCases { for i := range totalSizeErrorCases {
errs := ValidateAnnotations(totalSizeErrorCases[i], "field") errs := ValidateAnnotations(totalSizeErrorCases[i], validation.NewFieldPath("field"))
if len(errs) != 1 { if len(errs) != 1 {
t.Errorf("case[%d] expected failure", i) t.Errorf("case[%d] expected failure", i)
} }
@ -490,7 +509,7 @@ func TestValidateVolumes(t *testing.T) {
}}}}, }}}},
{Name: "fc", VolumeSource: api.VolumeSource{FC: &api.FCVolumeSource{[]string{"some_wwn"}, &lun, "ext4", false}}}, {Name: "fc", VolumeSource: api.VolumeSource{FC: &api.FCVolumeSource{[]string{"some_wwn"}, &lun, "ext4", false}}},
} }
names, errs := validateVolumes(successCase) names, errs := validateVolumes(successCase, validation.NewFieldPath("field"))
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
@ -543,46 +562,137 @@ func TestValidateVolumes(t *testing.T) {
F string F string
D string D string
}{ }{
"zero-length name": {[]api.Volume{{Name: "", VolumeSource: emptyVS}}, validation.ErrorTypeRequired, "[0].name", ""}, "zero-length name": {
"name > 63 characters": {[]api.Volume{{Name: strings.Repeat("a", 64), VolumeSource: emptyVS}}, validation.ErrorTypeInvalid, "[0].name", "must be a DNS label (at most 63 characters, matching regex [a-z0-9]([-a-z0-9]*[a-z0-9])?): e.g. \"my-name\""}, []api.Volume{{Name: "", VolumeSource: emptyVS}},
"name not a DNS label": {[]api.Volume{{Name: "a.b.c", VolumeSource: emptyVS}}, validation.ErrorTypeInvalid, "[0].name", "must be a DNS label (at most 63 characters, matching regex [a-z0-9]([-a-z0-9]*[a-z0-9])?): e.g. \"my-name\""}, validation.ErrorTypeRequired,
"name not unique": {[]api.Volume{{Name: "abc", VolumeSource: emptyVS}, {Name: "abc", VolumeSource: emptyVS}}, validation.ErrorTypeDuplicate, "[1].name", ""}, "name", "",
"empty portal": {[]api.Volume{{Name: "badportal", VolumeSource: emptyPortal}}, validation.ErrorTypeRequired, "[0].source.iscsi.targetPortal", ""}, },
"empty iqn": {[]api.Volume{{Name: "badiqn", VolumeSource: emptyIQN}}, validation.ErrorTypeRequired, "[0].source.iscsi.iqn", ""}, "name > 63 characters": {
"empty hosts": {[]api.Volume{{Name: "badhost", VolumeSource: emptyHosts}}, validation.ErrorTypeRequired, "[0].source.glusterfs.endpoints", ""}, []api.Volume{{Name: strings.Repeat("a", 64), VolumeSource: emptyVS}},
"empty path": {[]api.Volume{{Name: "badpath", VolumeSource: emptyPath}}, validation.ErrorTypeRequired, "[0].source.glusterfs.path", ""}, validation.ErrorTypeInvalid,
"empty datasetName": {[]api.Volume{{Name: "badname", VolumeSource: emptyName}}, validation.ErrorTypeRequired, "[0].source.flocker.datasetName", ""}, "name", "must be a DNS label",
"empty mon": {[]api.Volume{{Name: "badmon", VolumeSource: emptyMon}}, validation.ErrorTypeRequired, "[0].source.rbd.monitors", ""}, },
"empty image": {[]api.Volume{{Name: "badimage", VolumeSource: emptyImage}}, validation.ErrorTypeRequired, "[0].source.rbd.image", ""}, "name not a DNS label": {
"empty cephfs mon": {[]api.Volume{{Name: "badmon", VolumeSource: emptyCephFSMon}}, validation.ErrorTypeRequired, "[0].source.cephfs.monitors", ""}, []api.Volume{{Name: "a.b.c", VolumeSource: emptyVS}},
"empty metatada path": {[]api.Volume{{Name: "emptyname", VolumeSource: emptyPathName}}, validation.ErrorTypeRequired, "[0].source.downwardApi.path", ""}, validation.ErrorTypeInvalid,
"absolute path": {[]api.Volume{{Name: "absolutepath", VolumeSource: absolutePathName}}, validation.ErrorTypeForbidden, "[0].source.downwardApi.path", ""}, "name", "must be a DNS label",
"dot dot path": {[]api.Volume{{Name: "dotdotpath", VolumeSource: dotDotInPath}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not contain \"..\""}, },
"dot dot file name": {[]api.Volume{{Name: "dotdotfilename", VolumeSource: dotDotPathName}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not start with \"..\""}, "name not unique": {
"dot dot first level dirent": {[]api.Volume{{Name: "dotdotdirfilename", VolumeSource: dotDotFirstLevelDirent}}, validation.ErrorTypeInvalid, "[0].source.downwardApi.path", "must not start with \"..\""}, []api.Volume{{Name: "abc", VolumeSource: emptyVS}, {Name: "abc", VolumeSource: emptyVS}},
"empty wwn": {[]api.Volume{{Name: "badimage", VolumeSource: zeroWWN}}, validation.ErrorTypeRequired, "[0].source.fc.targetWWNs", ""}, validation.ErrorTypeDuplicate,
"empty lun": {[]api.Volume{{Name: "badimage", VolumeSource: emptyLun}}, validation.ErrorTypeRequired, "[0].source.fc.lun", ""}, "[1].name", "",
"slash in datasetName": {[]api.Volume{{Name: "slashinname", VolumeSource: slashInName}}, validation.ErrorTypeInvalid, "[0].source.flocker.datasetName", "must not contain '/'"}, },
"starts with '..'": {[]api.Volume{{Name: "badprefix", VolumeSource: startsWithDots}}, validation.ErrorTypeInvalid, "[0].source.gitRepo.directory", "must not start with \"..\""}, "empty portal": {
"contains '..'": {[]api.Volume{{Name: "containsdots", VolumeSource: containsDots}}, validation.ErrorTypeInvalid, "[0].source.gitRepo.directory", "must not contain \"..\""}, []api.Volume{{Name: "badportal", VolumeSource: emptyPortal}},
"absolute target": {[]api.Volume{{Name: "absolutetarget", VolumeSource: absPath}}, validation.ErrorTypeForbidden, "[0].source.gitRepo.directory", ""}, validation.ErrorTypeRequired,
"iscsi.targetPortal", "",
},
"empty iqn": {
[]api.Volume{{Name: "badiqn", VolumeSource: emptyIQN}},
validation.ErrorTypeRequired,
"iscsi.iqn", "",
},
"empty hosts": {
[]api.Volume{{Name: "badhost", VolumeSource: emptyHosts}},
validation.ErrorTypeRequired,
"glusterfs.endpoints", "",
},
"empty path": {
[]api.Volume{{Name: "badpath", VolumeSource: emptyPath}},
validation.ErrorTypeRequired,
"glusterfs.path", "",
},
"empty datasetName": {
[]api.Volume{{Name: "badname", VolumeSource: emptyName}},
validation.ErrorTypeRequired,
"flocker.datasetName", "",
},
"empty mon": {
[]api.Volume{{Name: "badmon", VolumeSource: emptyMon}},
validation.ErrorTypeRequired,
"rbd.monitors", "",
},
"empty image": {
[]api.Volume{{Name: "badimage", VolumeSource: emptyImage}},
validation.ErrorTypeRequired,
"rbd.image", "",
},
"empty cephfs mon": {
[]api.Volume{{Name: "badmon", VolumeSource: emptyCephFSMon}},
validation.ErrorTypeRequired,
"cephfs.monitors", "",
},
"empty metatada path": {
[]api.Volume{{Name: "emptyname", VolumeSource: emptyPathName}},
validation.ErrorTypeRequired,
"downwardAPI.path", "",
},
"absolute path": {
[]api.Volume{{Name: "absolutepath", VolumeSource: absolutePathName}},
validation.ErrorTypeForbidden,
"downwardAPI.path", "",
},
"dot dot path": {
[]api.Volume{{Name: "dotdotpath", VolumeSource: dotDotInPath}},
validation.ErrorTypeInvalid,
"downwardAPI.path", `must not contain ".."`,
},
"dot dot file name": {
[]api.Volume{{Name: "dotdotfilename", VolumeSource: dotDotPathName}},
validation.ErrorTypeInvalid,
"downwardAPI.path", `must not start with ".."`,
},
"dot dot first level dirent": {
[]api.Volume{{Name: "dotdotdirfilename", VolumeSource: dotDotFirstLevelDirent}},
validation.ErrorTypeInvalid,
"downwardAPI.path", `must not start with ".."`,
},
"empty wwn": {
[]api.Volume{{Name: "badimage", VolumeSource: zeroWWN}},
validation.ErrorTypeRequired,
"fc.targetWWNs", "",
},
"empty lun": {
[]api.Volume{{Name: "badimage", VolumeSource: emptyLun}},
validation.ErrorTypeRequired,
"fc.lun", "",
},
"slash in datasetName": {
[]api.Volume{{Name: "slashinname", VolumeSource: slashInName}},
validation.ErrorTypeInvalid,
"flocker.datasetName", "must not contain '/'",
},
"starts with '..'": {
[]api.Volume{{Name: "badprefix", VolumeSource: startsWithDots}},
validation.ErrorTypeInvalid,
"gitRepo.directory", `must not start with ".."`,
},
"contains '..'": {
[]api.Volume{{Name: "containsdots", VolumeSource: containsDots}},
validation.ErrorTypeInvalid,
"gitRepo.directory", `must not contain ".."`,
},
"absolute target": {
[]api.Volume{{Name: "absolutetarget", VolumeSource: absPath}},
validation.ErrorTypeForbidden,
"gitRepo.directory", "",
},
} }
for k, v := range errorCases { for k, v := range errorCases {
_, errs := validateVolumes(v.V) _, errs := validateVolumes(v.V, validation.NewFieldPath("field"))
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected failure %s for %v", k, v.V) t.Errorf("expected failure %s for %v", k, v.V)
continue continue
} }
for i := range errs { for i := range errs {
if errs[i].Type != v.T { if errs[i].Type != v.T {
t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i]) t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type)
} }
if errs[i].Field != v.F { if !strings.Contains(errs[i].Field, v.F) {
t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i]) t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field)
} }
detail := errs[i].Detail if !strings.Contains(errs[i].Detail, v.D) {
if detail != v.D { t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail)
t.Errorf("%s: expected error detail \"%s\", got \"%s\"", k, v.D, detail)
} }
} }
} }
@ -596,14 +706,14 @@ func TestValidatePorts(t *testing.T) {
{Name: "do-re-me", ContainerPort: 84, Protocol: "UDP"}, {Name: "do-re-me", ContainerPort: 84, Protocol: "UDP"},
{ContainerPort: 85, Protocol: "TCP"}, {ContainerPort: 85, Protocol: "TCP"},
} }
if errs := validatePorts(successCase); len(errs) != 0 { if errs := validateContainerPorts(successCase, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
nonCanonicalCase := []api.ContainerPort{ nonCanonicalCase := []api.ContainerPort{
{ContainerPort: 80, Protocol: "TCP"}, {ContainerPort: 80, Protocol: "TCP"},
} }
if errs := validatePorts(nonCanonicalCase); len(errs) != 0 { if errs := validateContainerPorts(nonCanonicalCase, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
@ -613,35 +723,74 @@ func TestValidatePorts(t *testing.T) {
F string F string
D string D string
}{ }{
"name > 15 characters": {[]api.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}}, validation.ErrorTypeInvalid, "[0].name", PortNameErrorMsg}, "name > 15 characters": {
"name not a IANA svc name ": {[]api.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}}, validation.ErrorTypeInvalid, "[0].name", PortNameErrorMsg}, []api.ContainerPort{{Name: strings.Repeat("a", 16), ContainerPort: 80, Protocol: "TCP"}},
"name not a IANA svc name (i.e. a number)": {[]api.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}}, validation.ErrorTypeInvalid, "[0].name", PortNameErrorMsg}, validation.ErrorTypeInvalid,
"name not unique": {[]api.ContainerPort{ "name", PortNameErrorMsg,
{Name: "abc", ContainerPort: 80, Protocol: "TCP"}, },
{Name: "abc", ContainerPort: 81, Protocol: "TCP"}, "name not a IANA svc name ": {
}, validation.ErrorTypeDuplicate, "[1].name", ""}, []api.ContainerPort{{Name: "a.b.c", ContainerPort: 80, Protocol: "TCP"}},
"zero container port": {[]api.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}}, validation.ErrorTypeInvalid, "[0].containerPort", PortRangeErrorMsg}, validation.ErrorTypeInvalid,
"invalid container port": {[]api.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}}, validation.ErrorTypeInvalid, "[0].containerPort", PortRangeErrorMsg}, "name", PortNameErrorMsg,
"invalid host port": {[]api.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}}, validation.ErrorTypeInvalid, "[0].hostPort", PortRangeErrorMsg}, },
"invalid protocol case": {[]api.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}}, validation.ErrorTypeNotSupported, "[0].protocol", "supported values: TCP, UDP"}, "name not a IANA svc name (i.e. a number)": {
"invalid protocol": {[]api.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}}, validation.ErrorTypeNotSupported, "[0].protocol", "supported values: TCP, UDP"}, []api.ContainerPort{{Name: "80", ContainerPort: 80, Protocol: "TCP"}},
"protocol required": {[]api.ContainerPort{{Name: "abc", ContainerPort: 80}}, validation.ErrorTypeRequired, "[0].protocol", ""}, validation.ErrorTypeInvalid,
"name", PortNameErrorMsg,
},
"name not unique": {
[]api.ContainerPort{
{Name: "abc", ContainerPort: 80, Protocol: "TCP"},
{Name: "abc", ContainerPort: 81, Protocol: "TCP"},
},
validation.ErrorTypeDuplicate,
"[1].name", "",
},
"zero container port": {
[]api.ContainerPort{{ContainerPort: 0, Protocol: "TCP"}},
validation.ErrorTypeInvalid,
"containerPort", PortRangeErrorMsg,
},
"invalid container port": {
[]api.ContainerPort{{ContainerPort: 65536, Protocol: "TCP"}},
validation.ErrorTypeInvalid,
"containerPort", PortRangeErrorMsg,
},
"invalid host port": {
[]api.ContainerPort{{ContainerPort: 80, HostPort: 65536, Protocol: "TCP"}},
validation.ErrorTypeInvalid,
"hostPort", PortRangeErrorMsg,
},
"invalid protocol case": {
[]api.ContainerPort{{ContainerPort: 80, Protocol: "tcp"}},
validation.ErrorTypeNotSupported,
"protocol", "supported values: TCP, UDP",
},
"invalid protocol": {
[]api.ContainerPort{{ContainerPort: 80, Protocol: "ICMP"}},
validation.ErrorTypeNotSupported,
"protocol", "supported values: TCP, UDP",
},
"protocol required": {
[]api.ContainerPort{{Name: "abc", ContainerPort: 80}},
validation.ErrorTypeRequired,
"protocol", "",
},
} }
for k, v := range errorCases { for k, v := range errorCases {
errs := validatePorts(v.P) errs := validateContainerPorts(v.P, validation.NewFieldPath("field"))
if len(errs) == 0 { if len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} }
for i := range errs { for i := range errs {
if errs[i].Type != v.T { if errs[i].Type != v.T {
t.Errorf("%s: expected errors to have type %s: %v", k, v.T, errs[i]) t.Errorf("%s: expected error to have type %q: %q", k, v.T, errs[i].Type)
} }
if errs[i].Field != v.F { if !strings.Contains(errs[i].Field, v.F) {
t.Errorf("%s: expected errors to have field %s: %v", k, v.F, errs[i]) t.Errorf("%s: expected error field %q: %q", k, v.F, errs[i].Field)
} }
detail := errs[i].Detail if !strings.Contains(errs[i].Detail, v.D) {
if detail != v.D { t.Errorf("%s: expected error detail %q, got %q", k, v.D, errs[i].Detail)
t.Errorf("%s: expected error detail either empty or %s, got %s", k, v.D, detail)
} }
} }
} }
@ -663,7 +812,7 @@ func TestValidateEnv(t *testing.T) {
}, },
}, },
} }
if errs := validateEnv(successCase); len(errs) != 0 { if errs := validateEnv(successCase, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
@ -770,17 +919,17 @@ func TestValidateEnv(t *testing.T) {
}, },
}, },
}}, }},
expectedError: "[0].valueFrom.fieldRef.fieldPath: unsupported value 'status.phase', Details: supported values: metadata.name, metadata.namespace, status.podIP", expectedError: "valueFrom.fieldRef.fieldPath: unsupported value 'status.phase', Details: supported values: metadata.name, metadata.namespace, status.podIP",
}, },
} }
for _, tc := range errorCases { for _, tc := range errorCases {
if errs := validateEnv(tc.envs); len(errs) == 0 { if errs := validateEnv(tc.envs, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %s", tc.name) t.Errorf("expected failure for %s", tc.name)
} else { } else {
for i := range errs { for i := range errs {
str := errs[i].Error() str := errs[i].Error()
if str != "" && str != tc.expectedError { if str != "" && !strings.Contains(str, tc.expectedError) {
t.Errorf("%s: expected error detail either empty or %s, got %s", tc.name, tc.expectedError, str) t.Errorf("%s: expected error detail either empty or %q, got %q", tc.name, tc.expectedError, str)
} }
} }
} }
@ -795,7 +944,7 @@ func TestValidateVolumeMounts(t *testing.T) {
{Name: "123", MountPath: "/foo"}, {Name: "123", MountPath: "/foo"},
{Name: "abc-123", MountPath: "/bar"}, {Name: "abc-123", MountPath: "/bar"},
} }
if errs := validateVolumeMounts(successCase, volumes); len(errs) != 0 { if errs := validateVolumeMounts(successCase, volumes, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
@ -805,7 +954,7 @@ func TestValidateVolumeMounts(t *testing.T) {
"empty mountpath": {{Name: "abc", MountPath: ""}}, "empty mountpath": {{Name: "abc", MountPath: ""}},
} }
for k, v := range errorCases { for k, v := range errorCases {
if errs := validateVolumeMounts(v, volumes); len(errs) == 0 { if errs := validateVolumeMounts(v, volumes, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} }
} }
@ -823,7 +972,7 @@ func TestValidateProbe(t *testing.T) {
} }
for _, p := range successCases { for _, p := range successCases {
if errs := validateProbe(p); len(errs) != 0 { if errs := validateProbe(p, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
@ -835,7 +984,7 @@ func TestValidateProbe(t *testing.T) {
errorCases = append(errorCases, probe) errorCases = append(errorCases, probe)
} }
for _, p := range errorCases { for _, p := range errorCases {
if errs := validateProbe(p); len(errs) == 0 { if errs := validateProbe(p, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %v", p) t.Errorf("expected failure for %v", p)
} }
} }
@ -849,7 +998,7 @@ func TestValidateHandler(t *testing.T) {
{HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}}, {HTTPGet: &api.HTTPGetAction{Path: "/", Port: intstr.FromString("port"), Host: "", Scheme: "HTTP"}},
} }
for _, h := range successCases { for _, h := range successCases {
if errs := validateHandler(&h); len(errs) != 0 { if errs := validateHandler(&h, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
@ -862,7 +1011,7 @@ func TestValidateHandler(t *testing.T) {
{HTTPGet: &api.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}}, {HTTPGet: &api.HTTPGetAction{Path: "", Port: intstr.FromString(""), Host: ""}},
} }
for _, h := range errorCases { for _, h := range errorCases {
if errs := validateHandler(&h); len(errs) == 0 { if errs := validateHandler(&h, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %#v", h) t.Errorf("expected failure for %#v", h)
} }
} }
@ -901,7 +1050,7 @@ func TestValidatePullPolicy(t *testing.T) {
} }
for k, v := range testCases { for k, v := range testCases {
ctr := &v.Container ctr := &v.Container
errs := validatePullPolicy(ctr) errs := validatePullPolicy(ctr.ImagePullPolicy, validation.NewFieldPath("field"))
if len(errs) != 0 { if len(errs) != 0 {
t.Errorf("case[%s] expected success, got %#v", k, errs) t.Errorf("case[%s] expected success, got %#v", k, errs)
} }
@ -1017,7 +1166,7 @@ func TestValidateContainers(t *testing.T) {
}, },
{Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", SecurityContext: fakeValidSecurityContext(true)}, {Name: "abc-1234", Image: "image", ImagePullPolicy: "IfNotPresent", SecurityContext: fakeValidSecurityContext(true)},
} }
if errs := validateContainers(successCase, volumes); len(errs) != 0 { if errs := validateContainers(successCase, volumes, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
@ -1197,7 +1346,7 @@ func TestValidateContainers(t *testing.T) {
}, },
} }
for k, v := range errorCases { for k, v := range errorCases {
if errs := validateContainers(v, volumes); len(errs) == 0 { if errs := validateContainers(v, volumes, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} }
} }
@ -1210,7 +1359,7 @@ func TestValidateRestartPolicy(t *testing.T) {
api.RestartPolicyNever, api.RestartPolicyNever,
} }
for _, policy := range successCases { for _, policy := range successCases {
if errs := validateRestartPolicy(&policy); len(errs) != 0 { if errs := validateRestartPolicy(&policy, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
@ -1218,7 +1367,7 @@ func TestValidateRestartPolicy(t *testing.T) {
errorCases := []api.RestartPolicy{"", "newpolicy"} errorCases := []api.RestartPolicy{"", "newpolicy"}
for k, policy := range errorCases { for k, policy := range errorCases {
if errs := validateRestartPolicy(&policy); len(errs) == 0 { if errs := validateRestartPolicy(&policy, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %d", k) t.Errorf("expected failure for %d", k)
} }
} }
@ -1227,14 +1376,14 @@ func TestValidateRestartPolicy(t *testing.T) {
func TestValidateDNSPolicy(t *testing.T) { func TestValidateDNSPolicy(t *testing.T) {
successCases := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault, api.DNSPolicy(api.DNSClusterFirst)} successCases := []api.DNSPolicy{api.DNSClusterFirst, api.DNSDefault, api.DNSPolicy(api.DNSClusterFirst)}
for _, policy := range successCases { for _, policy := range successCases {
if errs := validateDNSPolicy(&policy); len(errs) != 0 { if errs := validateDNSPolicy(&policy, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
errorCases := []api.DNSPolicy{api.DNSPolicy("invalid")} errorCases := []api.DNSPolicy{api.DNSPolicy("invalid")}
for _, policy := range errorCases { for _, policy := range errorCases {
if errs := validateDNSPolicy(&policy); len(errs) == 0 { if errs := validateDNSPolicy(&policy, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %v", policy) t.Errorf("expected failure for %v", policy)
} }
} }
@ -1295,7 +1444,7 @@ func TestValidatePodSpec(t *testing.T) {
}, },
} }
for i := range successCases { for i := range successCases {
if errs := ValidatePodSpec(&successCases[i]); len(errs) != 0 { if errs := ValidatePodSpec(&successCases[i], validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("expected success: %v", errs) t.Errorf("expected success: %v", errs)
} }
} }
@ -1360,7 +1509,7 @@ func TestValidatePodSpec(t *testing.T) {
}, },
} }
for k, v := range failureCases { for k, v := range failureCases {
if errs := ValidatePodSpec(&v); len(errs) == 0 { if errs := ValidatePodSpec(&v, validation.NewFieldPath("field")); len(errs) == 0 {
t.Errorf("expected failure for %q", k) t.Errorf("expected failure for %q", k)
} }
} }
@ -2715,7 +2864,7 @@ func TestValidateNode(t *testing.T) {
"metadata.labels": true, "metadata.labels": true,
"metadata.annotations": true, "metadata.annotations": true,
"metadata.namespace": true, "metadata.namespace": true,
"spec.ExternalID": true, "spec.externalID": true,
} }
if expectedFields[field] == false { if expectedFields[field] == false {
t.Errorf("%s: missing prefix for: %v", k, errs[i]) t.Errorf("%s: missing prefix for: %v", k, errs[i])
@ -3035,7 +3184,7 @@ func TestValidateResourceNames(t *testing.T) {
{"kubernetes.io/will/not/work/", false}, {"kubernetes.io/will/not/work/", false},
} }
for k, item := range table { for k, item := range table {
err := validateResourceName(item.input, "sth") err := validateResourceName(item.input, validation.NewFieldPath("field"))
if len(err) != 0 && item.success { if len(err) != 0 && item.success {
t.Errorf("expected no failure for input %q", item.input) t.Errorf("expected no failure for input %q", item.input)
} else if len(err) == 0 && !item.success { } else if len(err) == 0 && !item.success {
@ -3158,7 +3307,7 @@ func TestValidateLimitRange(t *testing.T) {
}, },
}, },
}}, }},
"Default is not supported when limit type is Pod", "not supported when limit type is Pod",
}, },
"default-request-limit-type-pod": { "default-request-limit-type-pod": {
api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{
@ -3171,7 +3320,7 @@ func TestValidateLimitRange(t *testing.T) {
}, },
}, },
}}, }},
"DefaultRequest is not supported when limit type is Pod", "not supported when limit type is Pod",
}, },
"min value 100m is greater than max value 10m": { "min value 100m is greater than max value 10m": {
api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{
@ -3234,7 +3383,7 @@ func TestValidateLimitRange(t *testing.T) {
}, },
}, },
}}, }},
"maxLimitRequestRatio 800m is less than 1", "ratio 800m is less than 1",
}, },
"invalid spec maxLimitRequestRatio greater than max/min": { "invalid spec maxLimitRequestRatio greater than max/min": {
api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{ api.LimitRange{ObjectMeta: api.ObjectMeta{Name: "abc", Namespace: "foo"}, Spec: api.LimitRangeSpec{
@ -3247,7 +3396,7 @@ func TestValidateLimitRange(t *testing.T) {
}, },
}, },
}}, }},
"maxLimitRequestRatio 10 is greater than max/min = 4.000000", "ratio 10 is greater than max/min = 4.000000",
}, },
} }
@ -3259,7 +3408,7 @@ func TestValidateLimitRange(t *testing.T) {
for i := range errs { for i := range errs {
detail := errs[i].Detail detail := errs[i].Detail
if detail != v.D { if detail != v.D {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, v.D, detail) t.Errorf("[%s]: expected error detail either empty or %q, got %q", k, v.D, detail)
} }
} }
} }
@ -3362,13 +3511,8 @@ func TestValidateResourceQuota(t *testing.T) {
t.Errorf("expected failure for %s", k) t.Errorf("expected failure for %s", k)
} }
for i := range errs { for i := range errs {
field := errs[i].Field if errs[i].Detail != v.D {
detail := errs[i].Detail t.Errorf("[%s]: expected error detail either empty or %s, got %s", k, v.D, errs[i].Detail)
if field != "metadata.name" && field != "metadata.namespace" && !api.IsStandardResourceName(field) {
t.Errorf("%s: missing prefix for: %v", k, field)
}
if detail != v.D {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, v.D, detail)
} }
} }
} }
@ -4020,7 +4164,7 @@ func TestValidateSecurityContext(t *testing.T) {
"no run as user": {noRunAsUser}, "no run as user": {noRunAsUser},
} }
for k, v := range successCases { for k, v := range successCases {
if errs := ValidateSecurityContext(v.sc); len(errs) != 0 { if errs := ValidateSecurityContext(v.sc, validation.NewFieldPath("field")); len(errs) != 0 {
t.Errorf("Expected success for %s, got %v", k, errs) t.Errorf("Expected success for %s, got %v", k, errs)
} }
} }
@ -4050,7 +4194,7 @@ func TestValidateSecurityContext(t *testing.T) {
}, },
} }
for k, v := range errorCases { for k, v := range errorCases {
if errs := ValidateSecurityContext(v.sc); len(errs) == 0 || errs[0].Type != v.errorType || errs[0].Detail != v.errorDetail { if errs := ValidateSecurityContext(v.sc, validation.NewFieldPath("field")); len(errs) == 0 || errs[0].Type != v.errorType || errs[0].Detail != v.errorDetail {
t.Errorf("Expected error type %s with detail %s for %s, got %v", v.errorType, v.errorDetail, k, errs) t.Errorf("Expected error type %s with detail %s for %s, got %v", v.errorType, v.errorDetail, k, errs)
} }
} }

View File

@ -17,7 +17,6 @@ limitations under the License.
package validation package validation
import ( import (
"fmt"
"net" "net"
"regexp" "regexp"
"strconv" "strconv"
@ -39,77 +38,77 @@ func ValidateHorizontalPodAutoscalerName(name string, prefix bool) (bool, string
return apivalidation.ValidateReplicationControllerName(name, prefix) return apivalidation.ValidateReplicationControllerName(name, prefix)
} }
func validateHorizontalPodAutoscalerSpec(autoscaler extensions.HorizontalPodAutoscalerSpec) validation.ErrorList { func validateHorizontalPodAutoscalerSpec(autoscaler extensions.HorizontalPodAutoscalerSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if autoscaler.MinReplicas != nil && *autoscaler.MinReplicas < 1 { if autoscaler.MinReplicas != nil && *autoscaler.MinReplicas < 1 {
allErrs = append(allErrs, validation.NewInvalidError("minReplicas", autoscaler.MinReplicas, `must be bigger or equal to 1`)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("minReplicas"), autoscaler.MinReplicas, `must be greater than or equal to 1`))
} }
if autoscaler.MaxReplicas < 1 { if autoscaler.MaxReplicas < 1 {
allErrs = append(allErrs, validation.NewInvalidError("maxReplicas", autoscaler.MaxReplicas, `must be bigger or equal to 1`)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, `must be greater than or equal to 1`))
} }
if autoscaler.MinReplicas != nil && autoscaler.MaxReplicas < *autoscaler.MinReplicas { if autoscaler.MinReplicas != nil && autoscaler.MaxReplicas < *autoscaler.MinReplicas {
allErrs = append(allErrs, validation.NewInvalidError("maxReplicas", autoscaler.MaxReplicas, `must be bigger or equal to minReplicas`)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("maxReplicas"), autoscaler.MaxReplicas, `must be greater than or equal to minReplicas`))
} }
if autoscaler.CPUUtilization != nil && autoscaler.CPUUtilization.TargetPercentage < 1 { if autoscaler.CPUUtilization != nil && autoscaler.CPUUtilization.TargetPercentage < 1 {
allErrs = append(allErrs, validation.NewInvalidError("cpuUtilization.targetPercentage", autoscaler.CPUUtilization.TargetPercentage, `must be bigger or equal to 1`)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("cpuUtilization", "targetPercentage"), autoscaler.CPUUtilization.TargetPercentage, `must be greater than or equal to 1`))
} }
if refErrs := ValidateSubresourceReference(autoscaler.ScaleRef); len(refErrs) > 0 { if refErrs := ValidateSubresourceReference(autoscaler.ScaleRef, fldPath.Child("scaleRef")); len(refErrs) > 0 {
allErrs = append(allErrs, refErrs.Prefix("scaleRef")...) allErrs = append(allErrs, refErrs.Prefix("scaleRef")...)
} else if autoscaler.ScaleRef.Subresource != "scale" { } else if autoscaler.ScaleRef.Subresource != "scale" {
allErrs = append(allErrs, validation.NewNotSupportedError("scaleRef.subresource", autoscaler.ScaleRef.Subresource, []string{"scale"})) allErrs = append(allErrs, validation.NewNotSupportedError(fldPath.Child("scaleRef", "subresource"), autoscaler.ScaleRef.Subresource, []string{"scale"}))
} }
return allErrs return allErrs
} }
func ValidateSubresourceReference(ref extensions.SubresourceReference) validation.ErrorList { func ValidateSubresourceReference(ref extensions.SubresourceReference, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if len(ref.Kind) == 0 { if len(ref.Kind) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("kind")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("kind")))
} else if ok, msg := apivalidation.IsValidPathSegmentName(ref.Kind); !ok { } else if ok, msg := apivalidation.IsValidPathSegmentName(ref.Kind); !ok {
allErrs = append(allErrs, validation.NewInvalidError("kind", ref.Kind, msg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("kind"), ref.Kind, msg))
} }
if len(ref.Name) == 0 { if len(ref.Name) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("name")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("name")))
} else if ok, msg := apivalidation.IsValidPathSegmentName(ref.Name); !ok { } else if ok, msg := apivalidation.IsValidPathSegmentName(ref.Name); !ok {
allErrs = append(allErrs, validation.NewInvalidError("name", ref.Name, msg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("name"), ref.Name, msg))
} }
if len(ref.Subresource) == 0 { if len(ref.Subresource) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("subresource")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("subresource")))
} else if ok, msg := apivalidation.IsValidPathSegmentName(ref.Subresource); !ok { } else if ok, msg := apivalidation.IsValidPathSegmentName(ref.Subresource); !ok {
allErrs = append(allErrs, validation.NewInvalidError("subresource", ref.Subresource, msg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("subresource"), ref.Subresource, msg))
} }
return allErrs return allErrs
} }
func ValidateHorizontalPodAutoscaler(autoscaler *extensions.HorizontalPodAutoscaler) validation.ErrorList { func ValidateHorizontalPodAutoscaler(autoscaler *extensions.HorizontalPodAutoscaler) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&autoscaler.ObjectMeta, true, ValidateHorizontalPodAutoscalerName, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec)...) allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(autoscaler.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
func ValidateHorizontalPodAutoscalerUpdate(newAutoscler, oldAutoscaler *extensions.HorizontalPodAutoscaler) validation.ErrorList { func ValidateHorizontalPodAutoscalerUpdate(newAutoscaler, oldAutoscaler *extensions.HorizontalPodAutoscaler) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&newAutoscler.ObjectMeta, &oldAutoscaler.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&newAutoscaler.ObjectMeta, &oldAutoscaler.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscler.Spec)...) allErrs = append(allErrs, validateHorizontalPodAutoscalerSpec(newAutoscaler.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
func ValidateHorizontalPodAutoscalerStatusUpdate(controller, oldController *extensions.HorizontalPodAutoscaler) validation.ErrorList { func ValidateHorizontalPodAutoscalerStatusUpdate(controller, oldController *extensions.HorizontalPodAutoscaler) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, validation.NewFieldPath("metadata"))...)
status := controller.Status status := controller.Status
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.CurrentReplicas), "currentReplicas")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.CurrentReplicas), validation.NewFieldPath("status", "currentReplicas"))...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.DesiredReplicas), "desiredReplicas")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.DesiredReplicas), validation.NewFieldPath("status", "desiredReplicasa"))...)
return allErrs return allErrs
} }
func ValidateThirdPartyResourceUpdate(update, old *extensions.ThirdPartyResource) validation.ErrorList { func ValidateThirdPartyResourceUpdate(update, old *extensions.ThirdPartyResource) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateThirdPartyResource(update)...) allErrs = append(allErrs, ValidateThirdPartyResource(update)...)
return allErrs return allErrs
} }
@ -120,16 +119,16 @@ func ValidateThirdPartyResourceName(name string, prefix bool) (bool, string) {
func ValidateThirdPartyResource(obj *extensions.ThirdPartyResource) validation.ErrorList { func ValidateThirdPartyResource(obj *extensions.ThirdPartyResource) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateThirdPartyResourceName).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateThirdPartyResourceName, validation.NewFieldPath("metadata"))...)
versions := sets.String{} versions := sets.String{}
for ix := range obj.Versions { for ix := range obj.Versions {
version := &obj.Versions[ix] version := &obj.Versions[ix]
if len(version.Name) == 0 { if len(version.Name) == 0 {
allErrs = append(allErrs, validation.NewInvalidError("name", version, "name can not be empty")) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("versions").Index(ix).Child("name"), version, "can not be empty"))
} }
if versions.Has(version.Name) { if versions.Has(version.Name) {
allErrs = append(allErrs, validation.NewDuplicateError("version", version)) allErrs = append(allErrs, validation.NewDuplicateError(validation.NewFieldPath("versions").Index(ix).Child("name"), version))
} }
versions.Insert(version.Name) versions.Insert(version.Name)
} }
@ -139,39 +138,39 @@ func ValidateThirdPartyResource(obj *extensions.ThirdPartyResource) validation.E
// ValidateDaemonSet tests if required fields in the DaemonSet are set. // ValidateDaemonSet tests if required fields in the DaemonSet are set.
func ValidateDaemonSet(controller *extensions.DaemonSet) validation.ErrorList { func ValidateDaemonSet(controller *extensions.DaemonSet) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&controller.ObjectMeta, true, ValidateDaemonSetName).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&controller.ObjectMeta, true, ValidateDaemonSetName, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
// ValidateDaemonSetUpdate tests if required fields in the DaemonSet are set. // ValidateDaemonSetUpdate tests if required fields in the DaemonSet are set.
func ValidateDaemonSetUpdate(controller, oldController *extensions.DaemonSet) validation.ErrorList { func ValidateDaemonSetUpdate(controller, oldController *extensions.DaemonSet) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateDaemonSetSpec(&controller.Spec, validation.NewFieldPath("spec"))...)
allErrs = append(allErrs, ValidateDaemonSetTemplateUpdate(controller.Spec.Template, oldController.Spec.Template).Prefix("spec.template")...) allErrs = append(allErrs, ValidateDaemonSetTemplateUpdate(controller.Spec.Template, oldController.Spec.Template, validation.NewFieldPath("spec", "template"))...)
return allErrs return allErrs
} }
// validateDaemonSetStatus validates a DaemonSetStatus // validateDaemonSetStatus validates a DaemonSetStatus
func validateDaemonSetStatus(status *extensions.DaemonSetStatus) validation.ErrorList { func validateDaemonSetStatus(status *extensions.DaemonSetStatus, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.CurrentNumberScheduled), "currentNumberScheduled")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.CurrentNumberScheduled), fldPath.Child("currentNumberScheduled"))...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.NumberMisscheduled), "numberMisscheduled")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.NumberMisscheduled), fldPath.Child("numberMisscheduled"))...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.DesiredNumberScheduled), "desiredNumberScheduled")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.DesiredNumberScheduled), fldPath.Child("desiredNumberScheduled"))...)
return allErrs return allErrs
} }
// ValidateDaemonSetStatus validates tests if required fields in the DaemonSet Status section // ValidateDaemonSetStatus validates tests if required fields in the DaemonSet Status section
func ValidateDaemonSetStatusUpdate(controller, oldController *extensions.DaemonSet) validation.ErrorList { func ValidateDaemonSetStatusUpdate(controller, oldController *extensions.DaemonSet) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&controller.ObjectMeta, &oldController.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, validateDaemonSetStatus(&controller.Status)...) allErrs = append(allErrs, validateDaemonSetStatus(&controller.Status, validation.NewFieldPath("status"))...)
return allErrs return allErrs
} }
// ValidateDaemonSetTemplateUpdate tests that certain fields in the daemon set's pod template are not updated. // ValidateDaemonSetTemplateUpdate tests that certain fields in the daemon set's pod template are not updated.
func ValidateDaemonSetTemplateUpdate(podTemplate, oldPodTemplate *api.PodTemplateSpec) validation.ErrorList { func ValidateDaemonSetTemplateUpdate(podTemplate, oldPodTemplate *api.PodTemplateSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
podSpec := podTemplate.Spec podSpec := podTemplate.Spec
// podTemplate.Spec is not a pointer, so we can modify NodeSelector and NodeName directly. // podTemplate.Spec is not a pointer, so we can modify NodeSelector and NodeName directly.
@ -180,33 +179,33 @@ func ValidateDaemonSetTemplateUpdate(podTemplate, oldPodTemplate *api.PodTemplat
// In particular, we do not allow updates to container images at this point. // In particular, we do not allow updates to container images at this point.
if !api.Semantic.DeepEqual(oldPodTemplate.Spec, podSpec) { if !api.Semantic.DeepEqual(oldPodTemplate.Spec, podSpec) {
// TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff // TODO: Pinpoint the specific field that causes the invalid error after we have strategic merge diff
allErrs = append(allErrs, validation.NewInvalidError("spec", "content of spec is not printed out, please refer to the \"details\"", "may not update fields other than spec.nodeSelector")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("spec"), "content of spec is not printed out, please refer to the \"details\"", "may not update fields other than spec.nodeSelector"))
} }
return allErrs return allErrs
} }
// ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set. // ValidateDaemonSetSpec tests if required fields in the DaemonSetSpec are set.
func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec) validation.ErrorList { func ValidateDaemonSetSpec(spec *extensions.DaemonSetSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, ValidatePodSelector(spec.Selector)...) allErrs = append(allErrs, ValidatePodSelector(spec.Selector, fldPath.Child("selector"))...)
if spec.Template == nil { if spec.Template == nil {
allErrs = append(allErrs, validation.NewRequiredError("template")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("template")))
return allErrs return allErrs
} }
selector, err := extensions.PodSelectorAsSelector(spec.Selector) selector, err := extensions.PodSelectorAsSelector(spec.Selector)
if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) { if err == nil && !selector.Matches(labels.Set(spec.Template.Labels)) {
allErrs = append(allErrs, validation.NewInvalidError("template.metadata.labels", spec.Template.Labels, "selector does not match template")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "selector does not match template"))
} }
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(spec.Template).Prefix("template")...) allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(spec.Template, fldPath.Child("template"))...)
// Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid. // Daemons typically run on more than one node, so mark Read-Write persistent disks as invalid.
allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes).Prefix("template.spec.volumes")...) allErrs = append(allErrs, apivalidation.ValidateReadOnlyPersistentDisks(spec.Template.Spec.Volumes, fldPath.Child("template", "spec", "volumes"))...)
// RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec(). // RestartPolicy has already been first-order validated as per ValidatePodTemplateSpec().
if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways { if spec.Template.Spec.RestartPolicy != api.RestartPolicyAlways {
allErrs = append(allErrs, validation.NewNotSupportedError("template.spec.restartPolicy", spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)})) allErrs = append(allErrs, validation.NewNotSupportedError(fldPath.Child("template", "spec", "restartPolicy"), spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyAlways)}))
} }
return allErrs return allErrs
@ -224,14 +223,14 @@ func ValidateDeploymentName(name string, prefix bool) (bool, string) {
return apivalidation.NameIsDNSSubdomain(name, prefix) return apivalidation.NameIsDNSSubdomain(name, prefix)
} }
func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fieldName string) validation.ErrorList { func ValidatePositiveIntOrPercent(intOrPercent intstr.IntOrString, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if intOrPercent.Type == intstr.String { if intOrPercent.Type == intstr.String {
if !validation.IsValidPercent(intOrPercent.StrVal) { if !validation.IsValidPercent(intOrPercent.StrVal) {
allErrs = append(allErrs, validation.NewInvalidError(fieldName, intOrPercent, "value should be int(5) or percentage(5%)")) allErrs = append(allErrs, validation.NewInvalidError(fldPath, intOrPercent, "value should be int(5) or percentage(5%)"))
} }
} else if intOrPercent.Type == intstr.Int { } else if intOrPercent.Type == intstr.Int {
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(intOrPercent.IntVal), fieldName)...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(intOrPercent.IntValue()), fldPath)...)
} }
return allErrs return allErrs
} }
@ -252,69 +251,69 @@ func getIntOrPercentValue(intOrStringValue intstr.IntOrString) int {
return intOrStringValue.IntValue() return intOrStringValue.IntValue()
} }
func IsNotMoreThan100Percent(intOrStringValue intstr.IntOrString, fieldName string) validation.ErrorList { func IsNotMoreThan100Percent(intOrStringValue intstr.IntOrString, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
value, isPercent := getPercentValue(intOrStringValue) value, isPercent := getPercentValue(intOrStringValue)
if !isPercent || value <= 100 { if !isPercent || value <= 100 {
return nil return nil
} }
allErrs = append(allErrs, validation.NewInvalidError(fieldName, intOrStringValue, "should not be more than 100%")) allErrs = append(allErrs, validation.NewInvalidError(fldPath, intOrStringValue, "should not be more than 100%"))
return allErrs return allErrs
} }
func ValidateRollingUpdateDeployment(rollingUpdate *extensions.RollingUpdateDeployment, fieldName string) validation.ErrorList { func ValidateRollingUpdateDeployment(rollingUpdate *extensions.RollingUpdateDeployment, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fieldName+"maxUnavailable")...) allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fieldName+".maxSurge")...) allErrs = append(allErrs, ValidatePositiveIntOrPercent(rollingUpdate.MaxSurge, fldPath.Child("maxSurge"))...)
if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 { if getIntOrPercentValue(rollingUpdate.MaxUnavailable) == 0 && getIntOrPercentValue(rollingUpdate.MaxSurge) == 0 {
// Both MaxSurge and MaxUnavailable cannot be zero. // Both MaxSurge and MaxUnavailable cannot be zero.
allErrs = append(allErrs, validation.NewInvalidError(fieldName+".maxUnavailable", rollingUpdate.MaxUnavailable, "cannot be 0 when maxSurge is 0 as well")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("maxUnavailable"), rollingUpdate.MaxUnavailable, "cannot be 0 when maxSurge is 0 as well"))
} }
// Validate that MaxUnavailable is not more than 100%. // Validate that MaxUnavailable is not more than 100%.
allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fieldName+".maxUnavailable")...) allErrs = append(allErrs, IsNotMoreThan100Percent(rollingUpdate.MaxUnavailable, fldPath.Child("maxUnavailable"))...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(rollingUpdate.MinReadySeconds), fieldName+".minReadySeconds")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(rollingUpdate.MinReadySeconds), fldPath.Child("minReadySeconds"))...)
return allErrs return allErrs
} }
func ValidateDeploymentStrategy(strategy *extensions.DeploymentStrategy, fieldName string) validation.ErrorList { func ValidateDeploymentStrategy(strategy *extensions.DeploymentStrategy, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if strategy.RollingUpdate == nil { if strategy.RollingUpdate == nil {
return allErrs return allErrs
} }
switch strategy.Type { switch strategy.Type {
case extensions.RecreateDeploymentStrategyType: case extensions.RecreateDeploymentStrategyType:
allErrs = append(allErrs, validation.NewForbiddenError("rollingUpdate", "rollingUpdate should be nil when strategy type is "+extensions.RecreateDeploymentStrategyType)) allErrs = append(allErrs, validation.NewForbiddenError(fldPath.Child("rollingUpdate"), "should be nil when strategy type is "+extensions.RecreateDeploymentStrategyType))
case extensions.RollingUpdateDeploymentStrategyType: case extensions.RollingUpdateDeploymentStrategyType:
allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, "rollingUpdate")...) allErrs = append(allErrs, ValidateRollingUpdateDeployment(strategy.RollingUpdate, fldPath.Child("rollingUpdate"))...)
} }
return allErrs return allErrs
} }
// Validates given deployment spec. // Validates given deployment spec.
func ValidateDeploymentSpec(spec *extensions.DeploymentSpec) validation.ErrorList { func ValidateDeploymentSpec(spec *extensions.DeploymentSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateNonEmptySelector(spec.Selector, "selector")...) allErrs = append(allErrs, apivalidation.ValidateNonEmptySelector(spec.Selector, fldPath.Child("selector"))...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(spec.Replicas), "replicas")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(spec.Replicas), fldPath.Child("replicas"))...)
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpecForRC(&spec.Template, spec.Selector, spec.Replicas, "template")...) allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpecForRC(&spec.Template, spec.Selector, spec.Replicas, fldPath.Child("template"))...)
allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, "strategy")...) allErrs = append(allErrs, ValidateDeploymentStrategy(&spec.Strategy, fldPath.Child("strategy"))...)
// empty string is a valid UniqueLabelKey // empty string is a valid UniqueLabelKey
if len(spec.UniqueLabelKey) > 0 { if len(spec.UniqueLabelKey) > 0 {
allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, "uniqueLabel")...) allErrs = append(allErrs, apivalidation.ValidateLabelName(spec.UniqueLabelKey, fldPath.Child("uniqueLabel"))...)
} }
return allErrs return allErrs
} }
func ValidateDeploymentUpdate(update, old *extensions.Deployment) validation.ErrorList { func ValidateDeploymentUpdate(update, old *extensions.Deployment) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&update.ObjectMeta, &old.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateDeploymentSpec(&update.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
func ValidateDeployment(obj *extensions.Deployment) validation.ErrorList { func ValidateDeployment(obj *extensions.Deployment) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&obj.ObjectMeta, true, ValidateDeploymentName, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateDeploymentSpec(&obj.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
@ -325,7 +324,7 @@ func ValidateThirdPartyResourceDataUpdate(update, old *extensions.ThirdPartyReso
func ValidateThirdPartyResourceData(obj *extensions.ThirdPartyResourceData) validation.ErrorList { func ValidateThirdPartyResourceData(obj *extensions.ThirdPartyResourceData) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if len(obj.Name) == 0 { if len(obj.Name) == 0 {
allErrs = append(allErrs, validation.NewInvalidError("name", obj.Name, "name must be non-empty")) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("name"), obj.Name, "must be non-empty"))
} }
return allErrs return allErrs
} }
@ -333,84 +332,84 @@ func ValidateThirdPartyResourceData(obj *extensions.ThirdPartyResourceData) vali
func ValidateJob(job *extensions.Job) validation.ErrorList { func ValidateJob(job *extensions.Job) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
// Jobs and rcs have the same name validation // Jobs and rcs have the same name validation
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&job.ObjectMeta, true, apivalidation.ValidateReplicationControllerName, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateJobSpec(&job.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateJobSpec(&job.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
func ValidateJobSpec(spec *extensions.JobSpec) validation.ErrorList { func ValidateJobSpec(spec *extensions.JobSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if spec.Parallelism != nil { if spec.Parallelism != nil {
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(*spec.Parallelism), "parallelism")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(*spec.Parallelism), fldPath.Child("parallelism"))...)
} }
if spec.Completions != nil { if spec.Completions != nil {
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(*spec.Completions), "completions")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(*spec.Completions), fldPath.Child("completions"))...)
} }
if spec.Selector == nil { if spec.Selector == nil {
allErrs = append(allErrs, validation.NewRequiredError("selector")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("selector")))
} else { } else {
allErrs = append(allErrs, ValidatePodSelector(spec.Selector).Prefix("selector")...) allErrs = append(allErrs, ValidatePodSelector(spec.Selector, fldPath.Child("selector"))...)
} }
if selector, err := extensions.PodSelectorAsSelector(spec.Selector); err == nil { if selector, err := extensions.PodSelectorAsSelector(spec.Selector); err == nil {
labels := labels.Set(spec.Template.Labels) labels := labels.Set(spec.Template.Labels)
if !selector.Matches(labels) { if !selector.Matches(labels) {
allErrs = append(allErrs, validation.NewInvalidError("template.metadata.labels", spec.Template.Labels, "selector does not match template")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("template", "metadata", "labels"), spec.Template.Labels, "selector does not match template"))
} }
} }
allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template).Prefix("template")...) allErrs = append(allErrs, apivalidation.ValidatePodTemplateSpec(&spec.Template, fldPath.Child("template"))...)
if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure && if spec.Template.Spec.RestartPolicy != api.RestartPolicyOnFailure &&
spec.Template.Spec.RestartPolicy != api.RestartPolicyNever { spec.Template.Spec.RestartPolicy != api.RestartPolicyNever {
allErrs = append(allErrs, validation.NewNotSupportedError("template.spec.restartPolicy", allErrs = append(allErrs, validation.NewNotSupportedError(fldPath.Child("template", "spec", "restartPolicy"),
spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)})) spec.Template.Spec.RestartPolicy, []string{string(api.RestartPolicyOnFailure), string(api.RestartPolicyNever)}))
} }
return allErrs return allErrs
} }
func ValidateJobStatus(status *extensions.JobStatus) validation.ErrorList { func ValidateJobStatus(status *extensions.JobStatus, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Active), "active")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Active), fldPath.Child("active"))...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Succeeded), "succeeded")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Succeeded), fldPath.Child("succeeded"))...)
allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Failed), "failed")...) allErrs = append(allErrs, apivalidation.ValidatePositiveField(int64(status.Failed), fldPath.Child("failed"))...)
return allErrs return allErrs
} }
func ValidateJobUpdate(job, oldJob *extensions.Job) validation.ErrorList { func ValidateJobUpdate(job, oldJob *extensions.Job) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateJobSpecUpdate(job.Spec, oldJob.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
func ValidateJobUpdateStatus(job, oldJob *extensions.Job) validation.ErrorList { func ValidateJobUpdateStatus(job, oldJob *extensions.Job) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&oldJob.ObjectMeta, &job.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status).Prefix("status")...) allErrs = append(allErrs, ValidateJobStatusUpdate(job.Status, oldJob.Status)...)
return allErrs return allErrs
} }
func ValidateJobSpecUpdate(spec, oldSpec extensions.JobSpec) validation.ErrorList { func ValidateJobSpecUpdate(spec, oldSpec extensions.JobSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, ValidateJobSpec(&spec)...) allErrs = append(allErrs, ValidateJobSpec(&spec, fldPath)...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, "completions")...) allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Completions, oldSpec.Completions, fldPath.Child("completions"))...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, "selector")...) allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Selector, oldSpec.Selector, fldPath.Child("selector"))...)
allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, "template")...) allErrs = append(allErrs, apivalidation.ValidateImmutableField(spec.Template, oldSpec.Template, fldPath.Child("template"))...)
return allErrs return allErrs
} }
func ValidateJobStatusUpdate(status, oldStatus extensions.JobStatus) validation.ErrorList { func ValidateJobStatusUpdate(status, oldStatus extensions.JobStatus) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, ValidateJobStatus(&status)...) allErrs = append(allErrs, ValidateJobStatus(&status, validation.NewFieldPath("status"))...)
return allErrs return allErrs
} }
// ValidateIngress tests if required fields in the Ingress are set. // ValidateIngress tests if required fields in the Ingress are set.
func ValidateIngress(ingress *extensions.Ingress) validation.ErrorList { func ValidateIngress(ingress *extensions.Ingress) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&ingress.ObjectMeta, true, ValidateIngressName, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
@ -420,16 +419,16 @@ func ValidateIngressName(name string, prefix bool) (bool, string) {
} }
// ValidateIngressSpec tests if required fields in the IngressSpec are set. // ValidateIngressSpec tests if required fields in the IngressSpec are set.
func ValidateIngressSpec(spec *extensions.IngressSpec) validation.ErrorList { func ValidateIngressSpec(spec *extensions.IngressSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
// TODO: Is a default backend mandatory? // TODO: Is a default backend mandatory?
if spec.Backend != nil { if spec.Backend != nil {
allErrs = append(allErrs, validateIngressBackend(spec.Backend).Prefix("backend")...) allErrs = append(allErrs, validateIngressBackend(spec.Backend, fldPath.Child("backend"))...)
} else if len(spec.Rules) == 0 { } else if len(spec.Rules) == 0 {
allErrs = append(allErrs, validation.NewInvalidError("rules", spec.Rules, "Either a default backend or a set of host rules are required for ingress.")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("rules"), spec.Rules, "Either a default backend or a set of host rules are required for ingress."))
} }
if len(spec.Rules) > 0 { if len(spec.Rules) > 0 {
allErrs = append(allErrs, validateIngressRules(spec.Rules).Prefix("rules")...) allErrs = append(allErrs, validateIngressRules(spec.Rules, fldPath.Child("rules"))...)
} }
return allErrs return allErrs
} }
@ -437,57 +436,57 @@ func ValidateIngressSpec(spec *extensions.IngressSpec) validation.ErrorList {
// ValidateIngressUpdate tests if required fields in the Ingress are set. // ValidateIngressUpdate tests if required fields in the Ingress are set.
func ValidateIngressUpdate(ingress, oldIngress *extensions.Ingress) validation.ErrorList { func ValidateIngressUpdate(ingress, oldIngress *extensions.Ingress) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec).Prefix("spec")...) allErrs = append(allErrs, ValidateIngressSpec(&ingress.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
// ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status. // ValidateIngressStatusUpdate tests if required fields in the Ingress are set when updating status.
func ValidateIngressStatusUpdate(ingress, oldIngress *extensions.Ingress) validation.ErrorList { func ValidateIngressStatusUpdate(ingress, oldIngress *extensions.Ingress) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMetaUpdate(&ingress.ObjectMeta, &oldIngress.ObjectMeta, validation.NewFieldPath("metadata"))...)
allErrs = append(allErrs, apivalidation.ValidateLoadBalancerStatus(&ingress.Status.LoadBalancer).Prefix("status.loadBalancer")...) allErrs = append(allErrs, apivalidation.ValidateLoadBalancerStatus(&ingress.Status.LoadBalancer, validation.NewFieldPath("status", "loadBalancer"))...)
return allErrs return allErrs
} }
func validateIngressRules(IngressRules []extensions.IngressRule) validation.ErrorList { func validateIngressRules(IngressRules []extensions.IngressRule, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if len(IngressRules) == 0 { if len(IngressRules) == 0 {
return append(allErrs, validation.NewRequiredError("IngressRules")) return append(allErrs, validation.NewRequiredError(fldPath))
} }
for _, ih := range IngressRules { for i, ih := range IngressRules {
if len(ih.Host) > 0 { if len(ih.Host) > 0 {
// TODO: Ports and ips are allowed in the host part of a url // TODO: Ports and ips are allowed in the host part of a url
// according to RFC 3986, consider allowing them. // according to RFC 3986, consider allowing them.
if valid, errMsg := apivalidation.NameIsDNSSubdomain(ih.Host, false); !valid { if valid, errMsg := apivalidation.NameIsDNSSubdomain(ih.Host, false); !valid {
allErrs = append(allErrs, validation.NewInvalidError("host", ih.Host, errMsg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Index(i).Child("host"), ih.Host, errMsg))
} }
if isIP := (net.ParseIP(ih.Host) != nil); isIP { if isIP := (net.ParseIP(ih.Host) != nil); isIP {
allErrs = append(allErrs, validation.NewInvalidError("host", ih.Host, "Host must be a DNS name, not ip address")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Index(i).Child("host"), ih.Host, "Host must be a DNS name, not ip address"))
} }
} }
allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue).Prefix("ingressRule")...) allErrs = append(allErrs, validateIngressRuleValue(&ih.IngressRuleValue, fldPath.Index(0))...)
} }
return allErrs return allErrs
} }
func validateIngressRuleValue(ingressRule *extensions.IngressRuleValue) validation.ErrorList { func validateIngressRuleValue(ingressRule *extensions.IngressRuleValue, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if ingressRule.HTTP != nil { if ingressRule.HTTP != nil {
allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP).Prefix("http")...) allErrs = append(allErrs, validateHTTPIngressRuleValue(ingressRule.HTTP, fldPath.Child("http"))...)
} }
return allErrs return allErrs
} }
func validateHTTPIngressRuleValue(httpIngressRuleValue *extensions.HTTPIngressRuleValue) validation.ErrorList { func validateHTTPIngressRuleValue(httpIngressRuleValue *extensions.HTTPIngressRuleValue, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if len(httpIngressRuleValue.Paths) == 0 { if len(httpIngressRuleValue.Paths) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("paths")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("paths")))
} }
for _, rule := range httpIngressRuleValue.Paths { for i, rule := range httpIngressRuleValue.Paths {
if len(rule.Path) > 0 { if len(rule.Path) > 0 {
if !strings.HasPrefix(rule.Path, "/") { if !strings.HasPrefix(rule.Path, "/") {
allErrs = append(allErrs, validation.NewInvalidError("path", rule.Path, "path must begin with /")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must begin with /"))
} }
// TODO: More draconian path regex validation. // TODO: More draconian path regex validation.
// Path must be a valid regex. This is the basic requirement. // Path must be a valid regex. This is the basic requirement.
@ -500,57 +499,57 @@ func validateHTTPIngressRuleValue(httpIngressRuleValue *extensions.HTTPIngressRu
// the user is confusing url regexes with path regexes. // the user is confusing url regexes with path regexes.
_, err := regexp.CompilePOSIX(rule.Path) _, err := regexp.CompilePOSIX(rule.Path)
if err != nil { if err != nil {
allErrs = append(allErrs, validation.NewInvalidError("path", rule.Path, "httpIngressRuleValue.path must be a valid regex.")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("paths").Index(i).Child("path"), rule.Path, "must be a valid regex."))
} }
} }
allErrs = append(allErrs, validateIngressBackend(&rule.Backend).Prefix("backend")...) allErrs = append(allErrs, validateIngressBackend(&rule.Backend, fldPath.Child("backend"))...)
} }
return allErrs return allErrs
} }
// validateIngressBackend tests if a given backend is valid. // validateIngressBackend tests if a given backend is valid.
func validateIngressBackend(backend *extensions.IngressBackend) validation.ErrorList { func validateIngressBackend(backend *extensions.IngressBackend, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
// All backends must reference a single local service by name, and a single service port by name or number. // All backends must reference a single local service by name, and a single service port by name or number.
if len(backend.ServiceName) == 0 { if len(backend.ServiceName) == 0 {
return append(allErrs, validation.NewRequiredError("serviceName")) return append(allErrs, validation.NewRequiredError(fldPath.Child("serviceName")))
} else if ok, errMsg := apivalidation.ValidateServiceName(backend.ServiceName, false); !ok { } else if ok, errMsg := apivalidation.ValidateServiceName(backend.ServiceName, false); !ok {
allErrs = append(allErrs, validation.NewInvalidError("serviceName", backend.ServiceName, errMsg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("serviceName"), backend.ServiceName, errMsg))
} }
if backend.ServicePort.Type == intstr.String { if backend.ServicePort.Type == intstr.String {
if !validation.IsDNS1123Label(backend.ServicePort.StrVal) { if !validation.IsDNS1123Label(backend.ServicePort.StrVal) {
allErrs = append(allErrs, validation.NewInvalidError("servicePort", backend.ServicePort.StrVal, apivalidation.DNS1123LabelErrorMsg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("servicePort"), backend.ServicePort.StrVal, apivalidation.DNS1123LabelErrorMsg))
} }
if !validation.IsValidPortName(backend.ServicePort.StrVal) { if !validation.IsValidPortName(backend.ServicePort.StrVal) {
allErrs = append(allErrs, validation.NewInvalidError("servicePort", backend.ServicePort.StrVal, apivalidation.PortNameErrorMsg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("servicePort"), backend.ServicePort.StrVal, apivalidation.PortNameErrorMsg))
} }
} else if !validation.IsValidPortNum(backend.ServicePort.IntValue()) { } else if !validation.IsValidPortNum(backend.ServicePort.IntValue()) {
allErrs = append(allErrs, validation.NewInvalidError("servicePort", backend.ServicePort, apivalidation.PortRangeErrorMsg)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("servicePort"), backend.ServicePort, apivalidation.PortRangeErrorMsg))
} }
return allErrs return allErrs
} }
func validateClusterAutoscalerSpec(spec extensions.ClusterAutoscalerSpec) validation.ErrorList { func validateClusterAutoscalerSpec(spec extensions.ClusterAutoscalerSpec, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if spec.MinNodes < 0 { if spec.MinNodes < 0 {
allErrs = append(allErrs, validation.NewInvalidError("minNodes", spec.MinNodes, `must be non-negative`)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("minNodes"), spec.MinNodes, `must be non-negative`))
} }
if spec.MaxNodes < spec.MinNodes { if spec.MaxNodes < spec.MinNodes {
allErrs = append(allErrs, validation.NewInvalidError("maxNodes", spec.MaxNodes, `must be bigger or equal to minNodes`)) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("maxNodes"), spec.MaxNodes, `must be greater than or equal to minNodes`))
} }
if len(spec.TargetUtilization) == 0 { if len(spec.TargetUtilization) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("targetUtilization")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("targetUtilization")))
} }
for _, target := range spec.TargetUtilization { for _, target := range spec.TargetUtilization {
if len(target.Resource) == 0 { if len(target.Resource) == 0 {
allErrs = append(allErrs, validation.NewRequiredError("targetUtilization.resource")) allErrs = append(allErrs, validation.NewRequiredError(fldPath.Child("targetUtilization", "resource")))
} }
if target.Value <= 0 { if target.Value <= 0 {
allErrs = append(allErrs, validation.NewInvalidError("targetUtilization.value", target.Value, "must be greater than 0")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("targetUtilization", "value"), target.Value, "must be greater than 0"))
} }
if target.Value > 1 { if target.Value > 1 {
allErrs = append(allErrs, validation.NewInvalidError("targetUtilization.value", target.Value, "must be less or equal 1")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("targetUtilization", "value"), target.Value, "must be less or equal 1"))
} }
} }
return allErrs return allErrs
@ -559,51 +558,51 @@ func validateClusterAutoscalerSpec(spec extensions.ClusterAutoscalerSpec) valida
func ValidateClusterAutoscaler(autoscaler *extensions.ClusterAutoscaler) validation.ErrorList { func ValidateClusterAutoscaler(autoscaler *extensions.ClusterAutoscaler) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if autoscaler.Name != "ClusterAutoscaler" { if autoscaler.Name != "ClusterAutoscaler" {
allErrs = append(allErrs, validation.NewInvalidError("name", autoscaler.Name, `name must be ClusterAutoscaler`)) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("metadata", "name"), autoscaler.Name, `name must be ClusterAutoscaler`))
} }
if autoscaler.Namespace != api.NamespaceDefault { if autoscaler.Namespace != api.NamespaceDefault {
allErrs = append(allErrs, validation.NewInvalidError("namespace", autoscaler.Namespace, `namespace must be default`)) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("metadata", "namespace"), autoscaler.Namespace, `namespace must be default`))
} }
allErrs = append(allErrs, validateClusterAutoscalerSpec(autoscaler.Spec)...) allErrs = append(allErrs, validateClusterAutoscalerSpec(autoscaler.Spec, validation.NewFieldPath("spec"))...)
return allErrs return allErrs
} }
func ValidatePodSelector(ps *extensions.PodSelector) validation.ErrorList { func ValidatePodSelector(ps *extensions.PodSelector, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
if ps == nil { if ps == nil {
return allErrs return allErrs
} }
allErrs = append(allErrs, apivalidation.ValidateLabels(ps.MatchLabels, "matchLabels")...) allErrs = append(allErrs, apivalidation.ValidateLabels(ps.MatchLabels, fldPath.Child("matchLabels"))...)
for i, expr := range ps.MatchExpressions { for i, expr := range ps.MatchExpressions {
allErrs = append(allErrs, ValidatePodSelectorRequirement(expr).Prefix(fmt.Sprintf("matchExpressions.[%v]", i))...) allErrs = append(allErrs, ValidatePodSelectorRequirement(expr, fldPath.Child("matchExpressions").Index(i))...)
} }
return allErrs return allErrs
} }
func ValidatePodSelectorRequirement(sr extensions.PodSelectorRequirement) validation.ErrorList { func ValidatePodSelectorRequirement(sr extensions.PodSelectorRequirement, fldPath *validation.FieldPath) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
switch sr.Operator { switch sr.Operator {
case extensions.PodSelectorOpIn, extensions.PodSelectorOpNotIn: case extensions.PodSelectorOpIn, extensions.PodSelectorOpNotIn:
if len(sr.Values) == 0 { if len(sr.Values) == 0 {
allErrs = append(allErrs, validation.NewInvalidError("values", sr.Values, "must be non-empty when operator is In or NotIn")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("values"), sr.Values, "must be non-empty when operator is In or NotIn"))
} }
case extensions.PodSelectorOpExists, extensions.PodSelectorOpDoesNotExist: case extensions.PodSelectorOpExists, extensions.PodSelectorOpDoesNotExist:
if len(sr.Values) > 0 { if len(sr.Values) > 0 {
allErrs = append(allErrs, validation.NewInvalidError("values", sr.Values, "must be empty when operator is Exists or DoesNotExist")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("values"), sr.Values, "must be empty when operator is Exists or DoesNotExist"))
} }
default: default:
allErrs = append(allErrs, validation.NewInvalidError("operator", sr.Operator, "not a valid pod selector operator")) allErrs = append(allErrs, validation.NewInvalidError(fldPath.Child("operator"), sr.Operator, "not a valid pod selector operator"))
} }
allErrs = append(allErrs, apivalidation.ValidateLabelName(sr.Key, "key")...) allErrs = append(allErrs, apivalidation.ValidateLabelName(sr.Key, fldPath.Child("key"))...)
return allErrs return allErrs
} }
func ValidateScale(scale *extensions.Scale) validation.ErrorList { func ValidateScale(scale *extensions.Scale) validation.ErrorList {
allErrs := validation.ErrorList{} allErrs := validation.ErrorList{}
allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&scale.ObjectMeta, true, apivalidation.NameIsDNSSubdomain).Prefix("metadata")...) allErrs = append(allErrs, apivalidation.ValidateObjectMeta(&scale.ObjectMeta, true, apivalidation.NameIsDNSSubdomain, validation.NewFieldPath("metadata"))...)
if scale.Spec.Replicas < 0 { if scale.Spec.Replicas < 0 {
allErrs = append(allErrs, validation.NewInvalidError("spec.replicas", scale.Spec.Replicas, "must be non-negative")) allErrs = append(allErrs, validation.NewInvalidError(validation.NewFieldPath("spec", "replicas"), scale.Spec.Replicas, "must be non-negative"))
} }
return allErrs return allErrs

View File

@ -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) {
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) {
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) {
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) {
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) {
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) {
} }
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) {
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) {
}, },
}, },
}, },
`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,

View File

@ -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{}),

View File

@ -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)
} }

View File

@ -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 {
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
} }

View File

@ -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
} }

View File

@ -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}

View File

@ -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
} 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
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
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

View File

@ -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
} }

View File

@ -121,61 +121,61 @@ 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.

View File

@ -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) {
} }
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) {
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) {
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) {
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")