Implementation of KEP Feature Gate VolumeSubpathEnvExpansion
This commit is contained in:
@@ -1644,6 +1644,13 @@ type VolumeMount struct {
|
||||
// This field is beta in 1.10.
|
||||
// +optional
|
||||
MountPropagation *MountPropagationMode
|
||||
// Expanded path within the volume from which the container's volume should be mounted.
|
||||
// Behaves similarly to SubPath but environment variable references $(VAR_NAME) are expanded using the container's environment.
|
||||
// Defaults to "" (volume's root).
|
||||
// SubPathExpr and SubPath are mutually exclusive.
|
||||
// This field is alpha in 1.14.
|
||||
// +optional
|
||||
SubPathExpr string
|
||||
}
|
||||
|
||||
// MountPropagationMode describes mount propagation.
|
||||
|
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
2
pkg/apis/core/v1/zz_generated.conversion.go
generated
@@ -7372,6 +7372,7 @@ func autoConvert_v1_VolumeMount_To_core_VolumeMount(in *v1.VolumeMount, out *cor
|
||||
out.MountPath = in.MountPath
|
||||
out.SubPath = in.SubPath
|
||||
out.MountPropagation = (*core.MountPropagationMode)(unsafe.Pointer(in.MountPropagation))
|
||||
out.SubPathExpr = in.SubPathExpr
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -7386,6 +7387,7 @@ func autoConvert_core_VolumeMount_To_v1_VolumeMount(in *core.VolumeMount, out *v
|
||||
out.MountPath = in.MountPath
|
||||
out.SubPath = in.SubPath
|
||||
out.MountPropagation = (*v1.MountPropagationMode)(unsafe.Pointer(in.MountPropagation))
|
||||
out.SubPathExpr = in.SubPathExpr
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@@ -2264,6 +2264,14 @@ func ValidateVolumeMounts(mounts []core.VolumeMount, voldevices map[string]strin
|
||||
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPath, fldPath.Child("subPath"))...)
|
||||
}
|
||||
|
||||
if len(mnt.SubPathExpr) > 0 {
|
||||
if len(mnt.SubPath) > 0 {
|
||||
allErrs = append(allErrs, field.Invalid(idxPath.Child("subPathExpr"), mnt.SubPathExpr, "subPathExpr and subPath are mutually exclusive"))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateLocalDescendingPath(mnt.SubPathExpr, fldPath.Child("subPathExpr"))...)
|
||||
}
|
||||
|
||||
if mnt.MountPropagation != nil {
|
||||
allErrs = append(allErrs, validateMountPropagation(mnt.MountPropagation, container, fldPath.Child("mountPropagation"))...)
|
||||
}
|
||||
|
@@ -4816,6 +4816,230 @@ func TestValidateDisabledSubpath(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSubpathMutuallyExclusive(t *testing.T) {
|
||||
// Enable feature VolumeSubpathEnvExpansion and VolumeSubpath
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpathEnvExpansion, true)()
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, true)()
|
||||
|
||||
volumes := []core.Volume{
|
||||
{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
|
||||
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
|
||||
{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
|
||||
}
|
||||
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
|
||||
if len(v1err) > 0 {
|
||||
t.Errorf("Invalid test volume - expected success %v", v1err)
|
||||
return
|
||||
}
|
||||
|
||||
container := core.Container{
|
||||
SecurityContext: nil,
|
||||
}
|
||||
|
||||
goodVolumeDevices := []core.VolumeDevice{
|
||||
{Name: "xyz", DevicePath: "/foofoo"},
|
||||
{Name: "uvw", DevicePath: "/foofoo/share/test"},
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
mounts []core.VolumeMount
|
||||
expectError bool
|
||||
}{
|
||||
"subpath and subpathexpr not specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
"subpath expr specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
SubPathExpr: "$(POD_NAME)",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
"subpath specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
SubPath: "baz",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
"subpath and subpathexpr specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
SubPath: "baz",
|
||||
SubPathExpr: "$(POD_NAME)",
|
||||
},
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range cases {
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
|
||||
|
||||
if len(errs) != 0 && !test.expectError {
|
||||
t.Errorf("test %v failed: %+v", name, errs)
|
||||
}
|
||||
|
||||
if len(errs) == 0 && test.expectError {
|
||||
t.Errorf("test %v failed, expected error", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateDisabledSubpathExpr(t *testing.T) {
|
||||
// Enable feature VolumeSubpathEnvExpansion
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpathEnvExpansion, true)()
|
||||
|
||||
volumes := []core.Volume{
|
||||
{Name: "abc", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim1"}}},
|
||||
{Name: "abc-123", VolumeSource: core.VolumeSource{PersistentVolumeClaim: &core.PersistentVolumeClaimVolumeSource{ClaimName: "testclaim2"}}},
|
||||
{Name: "123", VolumeSource: core.VolumeSource{HostPath: &core.HostPathVolumeSource{Path: "/foo/baz", Type: newHostPathType(string(core.HostPathUnset))}}},
|
||||
}
|
||||
vols, v1err := ValidateVolumes(volumes, field.NewPath("field"))
|
||||
if len(v1err) > 0 {
|
||||
t.Errorf("Invalid test volume - expected success %v", v1err)
|
||||
return
|
||||
}
|
||||
|
||||
container := core.Container{
|
||||
SecurityContext: nil,
|
||||
}
|
||||
|
||||
goodVolumeDevices := []core.VolumeDevice{
|
||||
{Name: "xyz", DevicePath: "/foofoo"},
|
||||
{Name: "uvw", DevicePath: "/foofoo/share/test"},
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
mounts []core.VolumeMount
|
||||
expectError bool
|
||||
}{
|
||||
"subpath expr not specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
"subpath expr specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
SubPathExpr: "$(POD_NAME)",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range cases {
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
|
||||
|
||||
if len(errs) != 0 && !test.expectError {
|
||||
t.Errorf("test %v failed: %+v", name, errs)
|
||||
}
|
||||
|
||||
if len(errs) == 0 && test.expectError {
|
||||
t.Errorf("test %v failed, expected error", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Repeat with feature gate off
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpathEnvExpansion, false)()
|
||||
cases = map[string]struct {
|
||||
mounts []core.VolumeMount
|
||||
expectError bool
|
||||
}{
|
||||
"subpath expr not specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
"subpath expr specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
SubPathExpr: "$(POD_NAME)",
|
||||
},
|
||||
},
|
||||
false, // validation should not fail, dropping the field is handled in PrepareForCreate/PrepareForUpdate
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range cases {
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
|
||||
|
||||
if len(errs) != 0 && !test.expectError {
|
||||
t.Errorf("test %v failed: %+v", name, errs)
|
||||
}
|
||||
|
||||
if len(errs) == 0 && test.expectError {
|
||||
t.Errorf("test %v failed, expected error", name)
|
||||
}
|
||||
}
|
||||
|
||||
// Repeat with subpath feature gate off
|
||||
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.VolumeSubpath, false)()
|
||||
cases = map[string]struct {
|
||||
mounts []core.VolumeMount
|
||||
expectError bool
|
||||
}{
|
||||
"subpath expr not specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
"subpath expr specified": {
|
||||
[]core.VolumeMount{
|
||||
{
|
||||
Name: "abc-123",
|
||||
MountPath: "/bab",
|
||||
SubPathExpr: "$(POD_NAME)",
|
||||
},
|
||||
},
|
||||
false, // validation should not fail, dropping the field is handled in PrepareForCreate/PrepareForUpdate
|
||||
},
|
||||
}
|
||||
|
||||
for name, test := range cases {
|
||||
errs := ValidateVolumeMounts(test.mounts, GetVolumeDeviceMap(goodVolumeDevices), vols, &container, field.NewPath("field"))
|
||||
|
||||
if len(errs) != 0 && !test.expectError {
|
||||
t.Errorf("test %v failed: %+v", name, errs)
|
||||
}
|
||||
|
||||
if len(errs) == 0 && test.expectError {
|
||||
t.Errorf("test %v failed, expected error", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateMountPropagation(t *testing.T) {
|
||||
bTrue := true
|
||||
bFalse := false
|
||||
|
Reference in New Issue
Block a user