Merge pull request #22575 from MikaelCluseau/wip-issue-20466

Automatic merge from submit-queue

Add subPath to mount a child dir or file of a volumeMount

Allow users to specify a subPath in Container.volumeMounts so they can use a single volume for many mounts instead of creating many volumes. For instance, a user can now use a single PersistentVolume to store the Mysql database and the document root of an Apache server of a LAMP stack pod by mapping them to different subPaths in this single volume.

Also solves https://github.com/kubernetes/kubernetes/issues/20466.
This commit is contained in:
k8s-merge-robot 2016-05-08 08:45:15 -07:00
commit f2f3b49f58
22 changed files with 310 additions and 58 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -556,6 +556,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">subPath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Path within the volume from which the container&#8217;s volume should be mounted. Defaults to "" (volume&#8217;s root).</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -3972,7 +3979,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-05-02 18:47:09 UTC
Last updated 2016-05-06 04:28:34 UTC
</div>
</div>
</body>

View File

@ -770,6 +770,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">subPath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Path within the volume from which the container&#8217;s volume should be mounted. Defaults to "" (volume&#8217;s root).</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -5962,7 +5969,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-05-02 18:47:04 UTC
Last updated 2016-05-06 04:28:25 UTC
</div>
</div>
</body>

View File

@ -803,6 +803,13 @@ span.icon > [class^="icon-"], span.icon > [class*=" icon-"] { cursor: default; }
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
<tr>
<td class="tableblock halign-left valign-top"><p class="tableblock">subPath</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">Path within the volume from which the container&#8217;s volume should be mounted. Defaults to "" (volume&#8217;s root).</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">false</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -7839,7 +7846,7 @@ The resulting set of endpoints can be viewed as:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-05-02 18:46:58 UTC
Last updated 2016-05-06 04:28:14 UTC
</div>
</div>
</body>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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:])

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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