diff --git a/api/swagger-spec/apps_v1alpha1.json b/api/swagger-spec/apps_v1alpha1.json index f257ec83fa5..912d2f9ab9a 100644 --- a/api/swagger-spec/apps_v1alpha1.json +++ b/api/swagger-spec/apps_v1alpha1.json @@ -2031,6 +2031,10 @@ "mountPath": { "type": "string", "description": "Path within the container at which the volume should be mounted. Must not contain ':'." + }, + "subPath": { + "type": "string", + "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root)." } } }, diff --git a/api/swagger-spec/batch_v1.json b/api/swagger-spec/batch_v1.json index 5a7d7984cbe..2d33824ef8f 100644 --- a/api/swagger-spec/batch_v1.json +++ b/api/swagger-spec/batch_v1.json @@ -2036,6 +2036,10 @@ "mountPath": { "type": "string", "description": "Path within the container at which the volume should be mounted. Must not contain ':'." + }, + "subPath": { + "type": "string", + "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root)." } } }, diff --git a/api/swagger-spec/extensions_v1beta1.json b/api/swagger-spec/extensions_v1beta1.json index 31b821c99da..9cc2de272dc 100644 --- a/api/swagger-spec/extensions_v1beta1.json +++ b/api/swagger-spec/extensions_v1beta1.json @@ -7343,6 +7343,10 @@ "mountPath": { "type": "string", "description": "Path within the container at which the volume should be mounted. Must not contain ':'." + }, + "subPath": { + "type": "string", + "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root)." } } }, diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index f63a40c58ce..e4b83e56a78 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -17330,6 +17330,10 @@ "mountPath": { "type": "string", "description": "Path within the container at which the volume should be mounted. Must not contain ':'." + }, + "subPath": { + "type": "string", + "description": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root)." } } }, diff --git a/docs/api-reference/batch/v1/definitions.html b/docs/api-reference/batch/v1/definitions.html index 0179c135c64..c43fa8403a1 100755 --- a/docs/api-reference/batch/v1/definitions.html +++ b/docs/api-reference/batch/v1/definitions.html @@ -556,6 +556,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

string

+ +

subPath

+

Path within the volume from which the container’s volume should be mounted. Defaults to "" (volume’s root).

+

false

+

string

+ + @@ -3972,7 +3979,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i diff --git a/docs/api-reference/extensions/v1beta1/definitions.html b/docs/api-reference/extensions/v1beta1/definitions.html index 735350e17e5..2b03c063c78 100755 --- a/docs/api-reference/extensions/v1beta1/definitions.html +++ b/docs/api-reference/extensions/v1beta1/definitions.html @@ -770,6 +770,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

string

+ +

subPath

+

Path within the volume from which the container’s volume should be mounted. Defaults to "" (volume’s root).

+

false

+

string

+ + @@ -5962,7 +5969,7 @@ Both these may change in the future. Incoming requests are matched against the h diff --git a/docs/api-reference/v1/definitions.html b/docs/api-reference/v1/definitions.html index c1704502430..e92ae5bedab 100755 --- a/docs/api-reference/v1/definitions.html +++ b/docs/api-reference/v1/definitions.html @@ -803,6 +803,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }

string

+ +

subPath

+

Path within the volume from which the container’s volume should be mounted. Defaults to "" (volume’s root).

+

false

+

string

+ + @@ -7839,7 +7846,7 @@ The resulting set of endpoints can be viewed as:
diff --git a/hack/verify-flags/exceptions.txt b/hack/verify-flags/exceptions.txt index a6d5a48813b..78a813bca8c 100644 --- a/hack/verify-flags/exceptions.txt +++ b/hack/verify-flags/exceptions.txt @@ -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.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/e2e/host_path.go: fmt.Sprintf("--file_content_in_loop=%v", filePathInReader), diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index 7dc3cf37171..2a21495a904 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -2955,6 +2955,7 @@ func DeepCopy_api_VolumeMount(in VolumeMount, out *VolumeMount, c *conversion.Cl out.Name = in.Name out.ReadOnly = in.ReadOnly out.MountPath = in.MountPath + out.SubPath = in.SubPath return nil } diff --git a/pkg/api/types.generated.go b/pkg/api/types.generated.go index c832d7ca359..475341a1607 100644 --- a/pkg/api/types.generated.go +++ b/pkg/api/types.generated.go @@ -14340,13 +14340,14 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool + var yyq2 [4]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[1] = x.ReadOnly != false + yyq2[3] = x.SubPath != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) + r.EncodeArrayStart(4) } else { yynn2 = 2 for _, b := range yyq2 { @@ -14420,6 +14421,31 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeString(codecSelferC_UTF81234, string(x.MountPath)) } } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.SubPath)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("subPath")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym14 := z.EncBinary() + _ = yym14 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.SubPath)) + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -14499,6 +14525,12 @@ func (x *VolumeMount) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } else { x.MountPath = string(r.DecodeString()) } + case "subPath": + if r.TryDecodeAsNil() { + x.SubPath = "" + } else { + x.SubPath = string(r.DecodeString()) + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -14510,16 +14542,16 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj7 int - var yyb7 bool - var yyhl7 bool = l >= 0 - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + var yyj8 int + var yyb8 bool + var yyhl8 bool = l >= 0 + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -14529,13 +14561,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Name = string(r.DecodeString()) } - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -14545,13 +14577,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.ReadOnly = bool(r.DecodeBool()) } - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -14561,18 +14593,34 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.MountPath = string(r.DecodeString()) } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.SubPath = "" + } else { + x.SubPath = string(r.DecodeString()) + } for { - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj7-1, "") + z.DecStructFieldNotFound(yyj8-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -52291,7 +52339,7 @@ func (x codecSelfer1234) decSliceVolumeMount(v *[]VolumeMount, d *codec1978.Deco yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 40) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 56) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/pkg/api/types.go b/pkg/api/types.go index f06cbcc9bf2..4b6ec153b89 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -758,6 +758,9 @@ type VolumeMount struct { ReadOnly bool `json:"readOnly,omitempty"` // Required. Must not contain ':'. 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. diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index aaae9aba4b8..2b102bd0ed8 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -6556,6 +6556,7 @@ func autoConvert_v1_VolumeMount_To_api_VolumeMount(in *VolumeMount, out *api.Vol out.Name = in.Name out.ReadOnly = in.ReadOnly out.MountPath = in.MountPath + out.SubPath = in.SubPath return nil } @@ -6567,6 +6568,7 @@ func autoConvert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *Vol out.Name = in.Name out.ReadOnly = in.ReadOnly out.MountPath = in.MountPath + out.SubPath = in.SubPath return nil } diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index 5993baefbf7..376cc44a7a8 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -2916,6 +2916,7 @@ func DeepCopy_v1_VolumeMount(in VolumeMount, out *VolumeMount, c *conversion.Clo out.Name = in.Name out.ReadOnly = in.ReadOnly out.MountPath = in.MountPath + out.SubPath = in.SubPath return nil } diff --git a/pkg/api/v1/generated.pb.go b/pkg/api/v1/generated.pb.go index 57d5ca07c45..956ef7f0d0a 100644 --- a/pkg/api/v1/generated.pb.go +++ b/pkg/api/v1/generated.pb.go @@ -7164,6 +7164,10 @@ func (m *VolumeMount) MarshalTo(data []byte) (int, error) { i++ i = encodeVarintGenerated(data, i, uint64(len(m.MountPath))) i += copy(data[i:], m.MountPath) + data[i] = 0x22 + i++ + i = encodeVarintGenerated(data, i, uint64(len(m.SubPath))) + i += copy(data[i:], m.SubPath) return i, nil } @@ -9726,6 +9730,8 @@ func (m *VolumeMount) Size() (n int) { n += 2 l = len(m.MountPath) n += 1 + l + sovGenerated(uint64(l)) + l = len(m.SubPath) + n += 1 + l + sovGenerated(uint64(l)) return n } @@ -32282,6 +32288,35 @@ func (m *VolumeMount) Unmarshal(data []byte) error { } m.MountPath = string(data[iNdEx:postIndex]) iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SubPath", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := data[iNdEx] + iNdEx++ + stringLen |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthGenerated + } + postIndex := iNdEx + intStringLen + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SubPath = string(data[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(data[iNdEx:]) diff --git a/pkg/api/v1/generated.proto b/pkg/api/v1/generated.proto index 7a8294b3160..db63aef8e2c 100644 --- a/pkg/api/v1/generated.proto +++ b/pkg/api/v1/generated.proto @@ -2705,6 +2705,10 @@ message VolumeMount { // Path within the container at which the volume should be mounted. Must // not contain ':'. optional string mountPath = 3; + + // Path within the volume from which the container's volume should be mounted. + // Defaults to "" (volume's root). + optional string subPath = 4; } // Represents the source of a volume to mount. diff --git a/pkg/api/v1/types.generated.go b/pkg/api/v1/types.generated.go index 54f98733c34..5d53f54fdb8 100644 --- a/pkg/api/v1/types.generated.go +++ b/pkg/api/v1/types.generated.go @@ -13943,13 +13943,14 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) { } else { yysep2 := !z.EncBinary() yy2arr2 := z.EncBasicHandle().StructToArray - var yyq2 [3]bool + var yyq2 [4]bool _, _, _ = yysep2, yyq2, yy2arr2 const yyr2 bool = false yyq2[1] = x.ReadOnly != false + yyq2[3] = x.SubPath != "" var yynn2 int if yyr2 || yy2arr2 { - r.EncodeArrayStart(3) + r.EncodeArrayStart(4) } else { yynn2 = 2 for _, b := range yyq2 { @@ -14023,6 +14024,31 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeString(codecSelferC_UTF81234, string(x.MountPath)) } } + if yyr2 || yy2arr2 { + z.EncSendContainerState(codecSelfer_containerArrayElem1234) + if yyq2[3] { + yym13 := z.EncBinary() + _ = yym13 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.SubPath)) + } + } else { + r.EncodeString(codecSelferC_UTF81234, "") + } + } else { + if yyq2[3] { + z.EncSendContainerState(codecSelfer_containerMapKey1234) + r.EncodeString(codecSelferC_UTF81234, string("subPath")) + z.EncSendContainerState(codecSelfer_containerMapValue1234) + yym14 := z.EncBinary() + _ = yym14 + if false { + } else { + r.EncodeString(codecSelferC_UTF81234, string(x.SubPath)) + } + } + } if yyr2 || yy2arr2 { z.EncSendContainerState(codecSelfer_containerArrayEnd1234) } else { @@ -14102,6 +14128,12 @@ func (x *VolumeMount) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } else { x.MountPath = string(r.DecodeString()) } + case "subPath": + if r.TryDecodeAsNil() { + x.SubPath = "" + } else { + x.SubPath = string(r.DecodeString()) + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -14113,16 +14145,16 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer1234 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj7 int - var yyb7 bool - var yyhl7 bool = l >= 0 - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + var yyj8 int + var yyb8 bool + var yyhl8 bool = l >= 0 + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -14132,13 +14164,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.Name = string(r.DecodeString()) } - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -14148,13 +14180,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.ReadOnly = bool(r.DecodeBool()) } - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { z.DecSendContainerState(codecSelfer_containerArrayEnd1234) return } @@ -14164,18 +14196,34 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } else { x.MountPath = string(r.DecodeString()) } + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l + } else { + yyb8 = r.CheckBreak() + } + if yyb8 { + z.DecSendContainerState(codecSelfer_containerArrayEnd1234) + return + } + z.DecSendContainerState(codecSelfer_containerArrayElem1234) + if r.TryDecodeAsNil() { + x.SubPath = "" + } else { + x.SubPath = string(r.DecodeString()) + } for { - yyj7++ - if yyhl7 { - yyb7 = yyj7 > l + yyj8++ + if yyhl8 { + yyb8 = yyj8 > l } else { - yyb7 = r.CheckBreak() + yyb8 = r.CheckBreak() } - if yyb7 { + if yyb8 { break } z.DecSendContainerState(codecSelfer_containerArrayElem1234) - z.DecStructFieldNotFound(yyj7-1, "") + z.DecStructFieldNotFound(yyj8-1, "") } z.DecSendContainerState(codecSelfer_containerArrayEnd1234) } @@ -52344,7 +52392,7 @@ func (x codecSelfer1234) decSliceVolumeMount(v *[]VolumeMount, d *codec1978.Deco yyrg1 := len(yyv1) > 0 yyv21 := yyv1 - yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 40) + yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 56) if yyrt1 { if yyrl1 <= cap(yyv1) { yyv1 = yyv1[:yyrl1] diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index d1fe5b2095a..00381d20e83 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -883,6 +883,9 @@ type VolumeMount struct { // Path within the container at which the volume should be mounted. Must // not contain ':'. 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. diff --git a/pkg/api/v1/types_swagger_doc_generated.go b/pkg/api/v1/types_swagger_doc_generated.go index 190317bd22f..de7305a09fb 100644 --- a/pkg/api/v1/types_swagger_doc_generated.go +++ b/pkg/api/v1/types_swagger_doc_generated.go @@ -1628,6 +1628,7 @@ var map_VolumeMount = map[string]string{ "name": "This must match the Name of a Volume.", "readOnly": "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.", "mountPath": "Path within the container at which the volume should be mounted. Must not contain ':'.", + "subPath": "Path within the volume from which the container's volume should be mounted. Defaults to \"\" (volume's root).", } func (VolumeMount) SwaggerDoc() map[string]string { diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 7d2ac76751a..75bccb49331 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -727,6 +727,28 @@ func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSou 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: // 1. is not abs path // 2. does not contain '..' @@ -1149,6 +1171,9 @@ func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath allErrs = append(allErrs, field.Invalid(idxPath.Child("mountPath"), mnt.MountPath, "must be unique")) } mountpoints.Insert(mnt.MountPath) + if len(mnt.SubPath) > 0 { + allErrs = append(allErrs, validateSubPath(mnt.SubPath, fldPath.Child("subPath"))...) + } } return allErrs } diff --git a/pkg/api/validation/validation_test.go b/pkg/api/validation/validation_test.go index c7682cf78e4..eb1c325f04e 100644 --- a/pkg/api/validation/validation_test.go +++ b/pkg/api/validation/validation_test.go @@ -1213,6 +1213,10 @@ func TestValidateVolumeMounts(t *testing.T) { {Name: "abc", MountPath: "/foo"}, {Name: "123", MountPath: "/bar"}, {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 { t.Errorf("expected success: %v", errs) @@ -1224,6 +1228,10 @@ func TestValidateVolumeMounts(t *testing.T) { "empty mountpath": {{Name: "abc", MountPath: ""}}, "colon mountpath": {{Name: "abc", MountPath: "foo:bar"}}, "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 { if errs := validateVolumeMounts(v, volumes, field.NewPath("field")); len(errs) == 0 { diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index e93cb838ea5..dfc2a471168 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -1187,10 +1187,14 @@ func makeMounts(pod *api.Pod, podDir string, container *api.Container, hostName, vol.SELinuxLabeled = true relabelVolume = true } + hostPath := vol.Mounter.GetPath() + if mount.SubPath != "" { + hostPath = filepath.Join(hostPath, mount.SubPath) + } mounts = append(mounts, kubecontainer.Mount{ Name: mount.Name, ContainerPath: mount.MountPath, - HostPath: vol.Mounter.GetPath(), + HostPath: hostPath, ReadOnly: mount.ReadOnly, SELinuxRelabel: relabelVolume, }) diff --git a/test/e2e/host_path.go b/test/e2e/host_path.go index 58a5a1dc848..3f264b6b88c 100644 --- a/test/e2e/host_path.go +++ b/test/e2e/host_path.go @@ -88,6 +88,37 @@ var _ = framework.KubeDescribe("hostPath", func() { }, 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.