From 59b1bacd27f44ea944ff78fd8db0a5c3514796e1 Mon Sep 17 00:00:00 2001 From: Derek Carr Date: Wed, 14 Jun 2017 19:52:31 -0400 Subject: [PATCH] image name may not have leading or trailing whitespace --- pkg/api/validation/validation.go | 28 +++++++++++++++++++++++++++ pkg/api/validation/validation_test.go | 26 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 78e055dde9fd2..425d44d37eca6 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -1972,6 +1972,9 @@ func validateContainers(containers []api.Container, volumes sets.String, fldPath } else { allNames.Insert(ctr.Name) } + // TODO: do not validate leading and trailing whitespace to preserve backward compatibility. + // for example: https://github.com/openshift/origin/issues/14659 image = " " is special token in pod template + // others may have done similar if len(ctr.Image) == 0 { allErrs = append(allErrs, field.Required(idxPath.Child("image"), "")) } @@ -2211,12 +2214,33 @@ func ValidateTolerations(tolerations []api.Toleration, fldPath *field.Path) fiel return allErrors } +// validateContainersOnlyForPod does additional validation for containers on a pod versus a pod template +// it only does additive validation of fields not covered in validateContainers +func validateContainersOnlyForPod(containers []api.Container, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + for i, ctr := range containers { + idxPath := fldPath.Index(i) + if len(ctr.Image) != len(strings.TrimSpace(ctr.Image)) { + allErrs = append(allErrs, field.Invalid(idxPath.Child("image"), ctr.Image, "must not have leading or trailing whitespace")) + } + } + return allErrs +} + // ValidatePod tests if required fields in the pod are set. func ValidatePod(pod *api.Pod) field.ErrorList { fldPath := field.NewPath("metadata") allErrs := ValidateObjectMeta(&pod.ObjectMeta, true, ValidatePodName, fldPath) allErrs = append(allErrs, ValidatePodSpecificAnnotations(pod.ObjectMeta.Annotations, &pod.Spec, fldPath.Child("annotations"))...) allErrs = append(allErrs, ValidatePodSpec(&pod.Spec, field.NewPath("spec"))...) + + // we do additional validation only pertinent for pods and not pod templates + // this was done to preserve backwards compatibility + specPath := field.NewPath("spec") + + allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.Containers, specPath.Child("containers"))...) + allErrs = append(allErrs, validateContainersOnlyForPod(pod.Spec.InitContainers, specPath.Child("initContainers"))...) + return allErrs } @@ -2605,6 +2629,10 @@ func ValidateContainerUpdates(newContainers, oldContainers []api.Container, fldP if len(ctr.Image) == 0 { allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("image"), "")) } + // this is only called from ValidatePodUpdate so its safe to check leading/trailing whitespace. + if len(strings.TrimSpace(ctr.Image)) != len(ctr.Image) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("image"), ctr.Image, "must not have leading or trailing whitespace")) + } } return allErrs, false } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index b56c059ea45cc..671d0dd11f46d 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -3080,6 +3080,9 @@ func TestValidateContainers(t *testing.T) { successCase := []api.Container{ {Name: "abc", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + // backwards compatibility to ensure containers in pod template spec do not check for this + {Name: "def", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, + {Name: "ghi", Image: " some ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {Name: "123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, {Name: "abc-123", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}, { @@ -3234,6 +3237,7 @@ func TestValidateContainers(t *testing.T) { }) errorCases := map[string][]api.Container{ "zero-length name": {{Name: "", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + "zero-length-image": {{Name: "abc", Image: "", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, "name > 63 characters": {{Name: strings.Repeat("a", 64), Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, "name not a DNS label": {{Name: "a.b.c", Image: "image", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, "name not unique": { @@ -4214,6 +4218,28 @@ func TestValidatePod(t *testing.T) { }, }, }, + "image whitespace": { + expectedError: "spec.containers[0].image", + spec: api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: " ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + }, + "image leading and trailing whitespace": { + expectedError: "spec.containers[0].image", + spec: api.Pod{ + ObjectMeta: metav1.ObjectMeta{Name: "abc", Namespace: "ns"}, + Spec: api.PodSpec{ + RestartPolicy: api.RestartPolicyAlways, + DNSPolicy: api.DNSClusterFirst, + Containers: []api.Container{{Name: "ctr", Image: " something ", ImagePullPolicy: "IfNotPresent", TerminationMessagePolicy: "File"}}, + }, + }, + }, "bad namespace": { expectedError: "metadata.namespace", spec: api.Pod{