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": { "mountPath": {
"type": "string", "type": "string",
"description": "Path within the container at which the volume should be mounted. Must not contain ':'." "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": { "mountPath": {
"type": "string", "type": "string",
"description": "Path within the container at which the volume should be mounted. Must not contain ':'." "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": { "mountPath": {
"type": "string", "type": "string",
"description": "Path within the container at which the volume should be mounted. Must not contain ':'." "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": { "mountPath": {
"type": "string", "type": "string",
"description": "Path within the container at which the volume should be mounted. Must not contain ':'." "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"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </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> </tbody>
</table> </table>
@ -3972,7 +3979,7 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-05-02 18:47:09 UTC Last updated 2016-05-06 04:28:34 UTC
</div> </div>
</div> </div>
</body> </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"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </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> </tbody>
</table> </table>
@ -5962,7 +5969,7 @@ Both these may change in the future. Incoming requests are matched against the h
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-05-02 18:47:04 UTC Last updated 2016-05-06 04:28:25 UTC
</div> </div>
</div> </div>
</body> </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"><p class="tableblock">string</p></td>
<td class="tableblock halign-left valign-top"></td> <td class="tableblock halign-left valign-top"></td>
</tr> </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> </tbody>
</table> </table>
@ -7839,7 +7846,7 @@ The resulting set of endpoints can be viewed as:<br>
</div> </div>
<div id="footer"> <div id="footer">
<div id="footer-text"> <div id="footer-text">
Last updated 2016-05-02 18:46:58 UTC Last updated 2016-05-06 04:28:14 UTC
</div> </div>
</div> </div>
</body> </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.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),

View File

@ -2955,6 +2955,7 @@ func DeepCopy_api_VolumeMount(in VolumeMount, out *VolumeMount, c *conversion.Cl
out.Name = in.Name out.Name = in.Name
out.ReadOnly = in.ReadOnly out.ReadOnly = in.ReadOnly
out.MountPath = in.MountPath out.MountPath = in.MountPath
out.SubPath = in.SubPath
return nil return nil
} }

View File

@ -14340,13 +14340,14 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) {
} else { } else {
yysep2 := !z.EncBinary() yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [3]bool var yyq2 [4]bool
_, _, _ = yysep2, yyq2, yy2arr2 _, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false const yyr2 bool = false
yyq2[1] = x.ReadOnly != false yyq2[1] = x.ReadOnly != false
yyq2[3] = x.SubPath != ""
var yynn2 int var yynn2 int
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
r.EncodeArrayStart(3) r.EncodeArrayStart(4)
} else { } else {
yynn2 = 2 yynn2 = 2
for _, b := range yyq2 { for _, b := range yyq2 {
@ -14420,6 +14421,31 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) {
r.EncodeString(codecSelferC_UTF81234, string(x.MountPath)) 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 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234) z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else { } else {
@ -14499,6 +14525,12 @@ func (x *VolumeMount) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
} else { } else {
x.MountPath = string(r.DecodeString()) x.MountPath = string(r.DecodeString())
} }
case "subPath":
if r.TryDecodeAsNil() {
x.SubPath = ""
} else {
x.SubPath = string(r.DecodeString())
}
default: default:
z.DecStructFieldNotFound(-1, yys3) z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3 } // end switch yys3
@ -14510,16 +14542,16 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d) z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r _, _, _ = h, z, r
var yyj7 int var yyj8 int
var yyb7 bool var yyb8 bool
var yyhl7 bool = l >= 0 var yyhl8 bool = l >= 0
yyj7++ yyj8++
if yyhl7 { if yyhl8 {
yyb7 = yyj7 > l yyb8 = yyj8 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -14529,13 +14561,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} else { } else {
x.Name = string(r.DecodeString()) x.Name = string(r.DecodeString())
} }
yyj7++ yyj8++
if yyhl7 { if yyhl8 {
yyb7 = yyj7 > l yyb8 = yyj8 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -14545,13 +14577,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} else { } else {
x.ReadOnly = bool(r.DecodeBool()) x.ReadOnly = bool(r.DecodeBool())
} }
yyj7++ yyj8++
if yyhl7 { if yyhl8 {
yyb7 = yyj7 > l yyb8 = yyj8 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -14561,18 +14593,34 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} else { } else {
x.MountPath = string(r.DecodeString()) x.MountPath = string(r.DecodeString())
} }
for { yyj8++
yyj7++ if yyhl8 {
if yyhl7 { yyb8 = yyj8 > l
yyb7 = yyj7 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.SubPath = ""
} else {
x.SubPath = string(r.DecodeString())
}
for {
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
} else {
yyb8 = r.CheckBreak()
}
if yyb8 {
break break
} }
z.DecSendContainerState(codecSelfer_containerArrayElem1234) z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj7-1, "") z.DecStructFieldNotFound(yyj8-1, "")
} }
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
@ -52291,7 +52339,7 @@ func (x codecSelfer1234) decSliceVolumeMount(v *[]VolumeMount, d *codec1978.Deco
yyrg1 := len(yyv1) > 0 yyrg1 := len(yyv1) > 0
yyv21 := yyv1 yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 40) yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 56)
if yyrt1 { if yyrt1 {
if yyrl1 <= cap(yyv1) { if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1] yyv1 = yyv1[:yyrl1]

View File

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

View File

@ -6556,6 +6556,7 @@ func autoConvert_v1_VolumeMount_To_api_VolumeMount(in *VolumeMount, out *api.Vol
out.Name = in.Name out.Name = in.Name
out.ReadOnly = in.ReadOnly out.ReadOnly = in.ReadOnly
out.MountPath = in.MountPath out.MountPath = in.MountPath
out.SubPath = in.SubPath
return nil return nil
} }
@ -6567,6 +6568,7 @@ func autoConvert_api_VolumeMount_To_v1_VolumeMount(in *api.VolumeMount, out *Vol
out.Name = in.Name out.Name = in.Name
out.ReadOnly = in.ReadOnly out.ReadOnly = in.ReadOnly
out.MountPath = in.MountPath out.MountPath = in.MountPath
out.SubPath = in.SubPath
return nil return nil
} }

View File

@ -2916,6 +2916,7 @@ func DeepCopy_v1_VolumeMount(in VolumeMount, out *VolumeMount, c *conversion.Clo
out.Name = in.Name out.Name = in.Name
out.ReadOnly = in.ReadOnly out.ReadOnly = in.ReadOnly
out.MountPath = in.MountPath out.MountPath = in.MountPath
out.SubPath = in.SubPath
return nil return nil
} }

View File

@ -7164,6 +7164,10 @@ func (m *VolumeMount) MarshalTo(data []byte) (int, error) {
i++ i++
i = encodeVarintGenerated(data, i, uint64(len(m.MountPath))) i = encodeVarintGenerated(data, i, uint64(len(m.MountPath)))
i += copy(data[i:], 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 return i, nil
} }
@ -9726,6 +9730,8 @@ func (m *VolumeMount) Size() (n int) {
n += 2 n += 2
l = len(m.MountPath) l = len(m.MountPath)
n += 1 + l + sovGenerated(uint64(l)) n += 1 + l + sovGenerated(uint64(l))
l = len(m.SubPath)
n += 1 + l + sovGenerated(uint64(l))
return n return n
} }
@ -32282,6 +32288,35 @@ func (m *VolumeMount) Unmarshal(data []byte) error {
} }
m.MountPath = string(data[iNdEx:postIndex]) m.MountPath = string(data[iNdEx:postIndex])
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: default:
iNdEx = preIndex iNdEx = preIndex
skippy, err := skipGenerated(data[iNdEx:]) 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 // Path within the container at which the volume should be mounted. Must
// not contain ':'. // not contain ':'.
optional string mountPath = 3; 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. // Represents the source of a volume to mount.

View File

@ -13943,13 +13943,14 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) {
} else { } else {
yysep2 := !z.EncBinary() yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [3]bool var yyq2 [4]bool
_, _, _ = yysep2, yyq2, yy2arr2 _, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false const yyr2 bool = false
yyq2[1] = x.ReadOnly != false yyq2[1] = x.ReadOnly != false
yyq2[3] = x.SubPath != ""
var yynn2 int var yynn2 int
if yyr2 || yy2arr2 { if yyr2 || yy2arr2 {
r.EncodeArrayStart(3) r.EncodeArrayStart(4)
} else { } else {
yynn2 = 2 yynn2 = 2
for _, b := range yyq2 { for _, b := range yyq2 {
@ -14023,6 +14024,31 @@ func (x *VolumeMount) CodecEncodeSelf(e *codec1978.Encoder) {
r.EncodeString(codecSelferC_UTF81234, string(x.MountPath)) 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 { if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234) z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else { } else {
@ -14102,6 +14128,12 @@ func (x *VolumeMount) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) {
} else { } else {
x.MountPath = string(r.DecodeString()) x.MountPath = string(r.DecodeString())
} }
case "subPath":
if r.TryDecodeAsNil() {
x.SubPath = ""
} else {
x.SubPath = string(r.DecodeString())
}
default: default:
z.DecStructFieldNotFound(-1, yys3) z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3 } // end switch yys3
@ -14113,16 +14145,16 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
var h codecSelfer1234 var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d) z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r _, _, _ = h, z, r
var yyj7 int var yyj8 int
var yyb7 bool var yyb8 bool
var yyhl7 bool = l >= 0 var yyhl8 bool = l >= 0
yyj7++ yyj8++
if yyhl7 { if yyhl8 {
yyb7 = yyj7 > l yyb8 = yyj8 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -14132,13 +14164,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} else { } else {
x.Name = string(r.DecodeString()) x.Name = string(r.DecodeString())
} }
yyj7++ yyj8++
if yyhl7 { if yyhl8 {
yyb7 = yyj7 > l yyb8 = yyj8 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -14148,13 +14180,13 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} else { } else {
x.ReadOnly = bool(r.DecodeBool()) x.ReadOnly = bool(r.DecodeBool())
} }
yyj7++ yyj8++
if yyhl7 { if yyhl8 {
yyb7 = yyj7 > l yyb8 = yyj8 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return return
} }
@ -14164,18 +14196,34 @@ func (x *VolumeMount) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) {
} else { } else {
x.MountPath = string(r.DecodeString()) x.MountPath = string(r.DecodeString())
} }
for { yyj8++
yyj7++ if yyhl8 {
if yyhl7 { yyb8 = yyj8 > l
yyb7 = yyj7 > l
} else { } else {
yyb7 = r.CheckBreak() yyb8 = r.CheckBreak()
} }
if yyb7 { if yyb8 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.SubPath = ""
} else {
x.SubPath = string(r.DecodeString())
}
for {
yyj8++
if yyhl8 {
yyb8 = yyj8 > l
} else {
yyb8 = r.CheckBreak()
}
if yyb8 {
break break
} }
z.DecSendContainerState(codecSelfer_containerArrayElem1234) z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj7-1, "") z.DecStructFieldNotFound(yyj8-1, "")
} }
z.DecSendContainerState(codecSelfer_containerArrayEnd1234) z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
} }
@ -52344,7 +52392,7 @@ func (x codecSelfer1234) decSliceVolumeMount(v *[]VolumeMount, d *codec1978.Deco
yyrg1 := len(yyv1) > 0 yyrg1 := len(yyv1) > 0
yyv21 := yyv1 yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 40) yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 56)
if yyrt1 { if yyrt1 {
if yyrl1 <= cap(yyv1) { if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1] yyv1 = yyv1[:yyrl1]

View File

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

View File

@ -1628,6 +1628,7 @@ var map_VolumeMount = map[string]string{
"name": "This must match the Name of a Volume.", "name": "This must match the Name of a Volume.",
"readOnly": "Mounted read-only if true, read-write otherwise (false or unspecified). Defaults to false.", "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 ':'.", "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 { func (VolumeMount) SwaggerDoc() map[string]string {

View File

@ -727,6 +727,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 '..'
@ -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")) 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
} }

View File

@ -1213,6 +1213,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)
@ -1224,6 +1228,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 {

View File

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

View File

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