Introduce subPath in VolumeMount
This commit is contained in:
@@ -92,3 +92,4 @@ test/e2e/host_path.go: fmt.Sprintf("--retry_time=%d", retryDuration),
|
|||||||
test/images/mount-tester/mt.go: flag.BoolVar(&breakOnExpectedContent, "break_on_expected_content", true, "Break out of loop on expected content, (use with --file_content_in_loop flag only)")
|
test/images/mount-tester/mt.go: flag.BoolVar(&breakOnExpectedContent, "break_on_expected_content", true, "Break out of loop on expected content, (use with --file_content_in_loop flag only)")
|
||||||
test/images/mount-tester/mt.go: flag.IntVar(&retryDuration, "retry_time", 180, "Retry time during the loop")
|
test/images/mount-tester/mt.go: flag.IntVar(&retryDuration, "retry_time", 180, "Retry time during the loop")
|
||||||
test/images/mount-tester/mt.go: flag.StringVar(&readFileContentInLoopPath, "file_content_in_loop", "", "Path to read the file content in loop from")
|
test/images/mount-tester/mt.go: flag.StringVar(&readFileContentInLoopPath, "file_content_in_loop", "", "Path to read the file content in loop from")
|
||||||
|
test/e2e/host_path.go: fmt.Sprintf("--file_content_in_loop=%v", filePathInReader),
|
||||||
|
@@ -758,6 +758,9 @@ type VolumeMount struct {
|
|||||||
ReadOnly bool `json:"readOnly,omitempty"`
|
ReadOnly bool `json:"readOnly,omitempty"`
|
||||||
// Required. Must not contain ':'.
|
// Required. Must not contain ':'.
|
||||||
MountPath string `json:"mountPath"`
|
MountPath string `json:"mountPath"`
|
||||||
|
// Path within the volume from which the container's volume should be mounted.
|
||||||
|
// Defaults to "" (volume's root).
|
||||||
|
SubPath string `json:"subPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvVar represents an environment variable present in a Container.
|
// EnvVar represents an environment variable present in a Container.
|
||||||
|
@@ -883,6 +883,9 @@ type VolumeMount struct {
|
|||||||
// Path within the container at which the volume should be mounted. Must
|
// Path within the container at which the volume should be mounted. Must
|
||||||
// not contain ':'.
|
// not contain ':'.
|
||||||
MountPath string `json:"mountPath" protobuf:"bytes,3,opt,name=mountPath"`
|
MountPath string `json:"mountPath" protobuf:"bytes,3,opt,name=mountPath"`
|
||||||
|
// Path within the volume from which the container's volume should be mounted.
|
||||||
|
// Defaults to "" (volume's root).
|
||||||
|
SubPath string `json:"subPath,omitempty" protobuf:"bytes,4,opt,name=subPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EnvVar represents an environment variable present in a Container.
|
// EnvVar represents an environment variable present in a Container.
|
||||||
|
@@ -746,6 +746,28 @@ func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSou
|
|||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This validate will make sure targetPath:
|
||||||
|
// 1. is not abs path
|
||||||
|
// 2. does not start with '../'
|
||||||
|
// 3. does not contain '/../'
|
||||||
|
// 4. does not end with '/..'
|
||||||
|
func validateSubPath(targetPath string, fldPath *field.Path) field.ErrorList {
|
||||||
|
allErrs := field.ErrorList{}
|
||||||
|
if path.IsAbs(targetPath) {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must be a relative path"))
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(targetPath, "../") {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not start with '../'"))
|
||||||
|
}
|
||||||
|
if strings.Contains(targetPath, "/../") {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not contain '/../'"))
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(targetPath, "/..") {
|
||||||
|
allErrs = append(allErrs, field.Invalid(fldPath, targetPath, "must not end with '/..'"))
|
||||||
|
}
|
||||||
|
return allErrs
|
||||||
|
}
|
||||||
|
|
||||||
// This validate will make sure targetPath:
|
// This validate will make sure targetPath:
|
||||||
// 1. is not abs path
|
// 1. is not abs path
|
||||||
// 2. does not contain '..'
|
// 2. does not contain '..'
|
||||||
@@ -1168,6 +1190,9 @@ func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath
|
|||||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique"))
|
allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique"))
|
||||||
}
|
}
|
||||||
mountpoints.Insert(mnt.MountPath)
|
mountpoints.Insert(mnt.MountPath)
|
||||||
|
if len(mnt.SubPath) > 0 {
|
||||||
|
allErrs = append(allErrs, validateSubPath(mnt.SubPath, fldPath.Child("subPath"))...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return allErrs
|
return allErrs
|
||||||
}
|
}
|
||||||
|
@@ -1274,6 +1274,10 @@ func TestValidateVolumeMounts(t *testing.T) {
|
|||||||
{Name: "abc", MountPath: "/foo"},
|
{Name: "abc", MountPath: "/foo"},
|
||||||
{Name: "123", MountPath: "/bar"},
|
{Name: "123", MountPath: "/bar"},
|
||||||
{Name: "abc-123", MountPath: "/baz"},
|
{Name: "abc-123", MountPath: "/baz"},
|
||||||
|
{Name: "abc-123", MountPath: "/baa", SubPath: ""},
|
||||||
|
{Name: "abc-123", MountPath: "/bab", SubPath: "baz"},
|
||||||
|
{Name: "abc-123", MountPath: "/bac", SubPath: ".baz"},
|
||||||
|
{Name: "abc-123", MountPath: "/bad", SubPath: "..baz"},
|
||||||
}
|
}
|
||||||
if errs := validateVolumeMounts(successCase, volumes, field.NewPath("field")); len(errs) != 0 {
|
if errs := validateVolumeMounts(successCase, volumes, field.NewPath("field")); len(errs) != 0 {
|
||||||
t.Errorf("expected success: %v", errs)
|
t.Errorf("expected success: %v", errs)
|
||||||
@@ -1285,6 +1289,10 @@ func TestValidateVolumeMounts(t *testing.T) {
|
|||||||
"empty mountpath": {{Name: "abc", MountPath: ""}},
|
"empty mountpath": {{Name: "abc", MountPath: ""}},
|
||||||
"colon mountpath": {{Name: "abc", MountPath: "foo:bar"}},
|
"colon mountpath": {{Name: "abc", MountPath: "foo:bar"}},
|
||||||
"mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
|
"mountpath collision": {{Name: "foo", MountPath: "/path/a"}, {Name: "bar", MountPath: "/path/a"}},
|
||||||
|
"absolute subpath": {{Name: "abc", MountPath: "/bar", SubPath: "/baz"}},
|
||||||
|
"subpath in ..": {{Name: "abc", MountPath: "/bar", SubPath: "../baz"}},
|
||||||
|
"subpath contains ..": {{Name: "abc", MountPath: "/bar", SubPath: "baz/../bat"}},
|
||||||
|
"subpath ends in ..": {{Name: "abc", MountPath: "/bar", SubPath: "./.."}},
|
||||||
}
|
}
|
||||||
for k, v := range errorCases {
|
for k, v := range errorCases {
|
||||||
if errs := validateVolumeMounts(v, volumes, field.NewPath("field")); len(errs) == 0 {
|
if errs := validateVolumeMounts(v, volumes, field.NewPath("field")); len(errs) == 0 {
|
||||||
|
@@ -1176,10 +1176,14 @@ func makeMounts(pod *api.Pod, podDir string, container *api.Container, hostName,
|
|||||||
vol.SELinuxLabeled = true
|
vol.SELinuxLabeled = true
|
||||||
relabelVolume = true
|
relabelVolume = true
|
||||||
}
|
}
|
||||||
|
hostPath := vol.Mounter.GetPath()
|
||||||
|
if mount.SubPath != "" {
|
||||||
|
hostPath = filepath.Join(hostPath, mount.SubPath)
|
||||||
|
}
|
||||||
mounts = append(mounts, kubecontainer.Mount{
|
mounts = append(mounts, kubecontainer.Mount{
|
||||||
Name: mount.Name,
|
Name: mount.Name,
|
||||||
ContainerPath: mount.MountPath,
|
ContainerPath: mount.MountPath,
|
||||||
HostPath: vol.Mounter.GetPath(),
|
HostPath: hostPath,
|
||||||
ReadOnly: mount.ReadOnly,
|
ReadOnly: mount.ReadOnly,
|
||||||
SELinuxRelabel: relabelVolume,
|
SELinuxRelabel: relabelVolume,
|
||||||
})
|
})
|
||||||
|
@@ -88,6 +88,37 @@ var _ = framework.KubeDescribe("hostPath", func() {
|
|||||||
}, namespace.Name,
|
}, namespace.Name,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("should support subPath [Conformance]", func() {
|
||||||
|
volumePath := "/test-volume"
|
||||||
|
subPath := "sub-path"
|
||||||
|
fileName := "test-file"
|
||||||
|
retryDuration := 180
|
||||||
|
|
||||||
|
filePathInWriter := path.Join(volumePath, fileName)
|
||||||
|
filePathInReader := path.Join(volumePath, subPath, fileName)
|
||||||
|
|
||||||
|
source := &api.HostPathVolumeSource{
|
||||||
|
Path: "/tmp",
|
||||||
|
}
|
||||||
|
pod := testPodWithHostVol(volumePath, source)
|
||||||
|
// Write the file in the subPath from container 0
|
||||||
|
container := &pod.Spec.Containers[0]
|
||||||
|
container.VolumeMounts[0].SubPath = subPath
|
||||||
|
container.Args = []string{
|
||||||
|
fmt.Sprintf("--new_file_0644=%v", filePathInWriter),
|
||||||
|
fmt.Sprintf("--file_mode=%v", filePathInWriter),
|
||||||
|
}
|
||||||
|
// Read it from outside the subPath from container 1
|
||||||
|
pod.Spec.Containers[1].Args = []string{
|
||||||
|
fmt.Sprintf("--file_content_in_loop=%v", filePathInReader),
|
||||||
|
fmt.Sprintf("--retry_time=%d", retryDuration),
|
||||||
|
}
|
||||||
|
|
||||||
|
framework.TestContainerOutput("hostPath subPath", c, pod, 1, []string{
|
||||||
|
"content of file \"" + filePathInReader + "\": mount-tester new file",
|
||||||
|
}, namespace.Name)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
//These constants are borrowed from the other test.
|
//These constants are borrowed from the other test.
|
||||||
|
Reference in New Issue
Block a user