Merge pull request #25285 from ingvagabund/extend-secrets-volumes-with-path-control

Automatic merge from submit-queue

Extend secrets volumes with path control

As per [1] this PR extends secrets mapped into volume with:

* key-to-path mapping the same way as is for configmap. E.g.

```
{
 "apiVersion": "v1",
 "kind": "Pod",
  "metadata": {
    "name": "mypod",
    "namespace": "default"
  },
  "spec": {
    "containers": [{
      "name": "mypod",
      "image": "redis",
      "volumeMounts": [{
        "name": "foo",
        "mountPath": "/etc/foo",
        "readOnly": true
      }]
    }],
    "volumes": [{
      "name": "foo",
      "secret": {
        "secretName": "mysecret",
        "items": [{
          "key": "username",
          "path": "my-username"
        }]
      }
    }]
  }
}
```

Here the ``spec.volumes[0].secret.items`` added changing original target ``/etc/foo/username`` to ``/etc/foo/my-username``.

* secondly, refactoring ``pkg/volumes/secrets/secrets.go`` volume plugin to use ``AtomicWritter`` to project a secret into file.

[1] https://github.com/kubernetes/kubernetes/blob/master/docs/design/configmap.md#changes-to-secret
This commit is contained in:
k8s-merge-robot 2016-05-21 03:55:13 -07:00
commit eb733cbf45
20 changed files with 737 additions and 337 deletions

View File

@ -1405,6 +1405,31 @@
"secretName": {
"type": "string",
"description": "Name of the secret in the pod's namespace to use. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets"
},
"items": {
"type": "array",
"items": {
"$ref": "v1.KeyToPath"
},
"description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the '..' path or start with '..'."
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
@ -1780,24 +1805,6 @@
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -1410,6 +1410,31 @@
"secretName": {
"type": "string",
"description": "Name of the secret in the pod's namespace to use. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets"
},
"items": {
"type": "array",
"items": {
"$ref": "v1.KeyToPath"
},
"description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the '..' path or start with '..'."
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
@ -1785,24 +1810,6 @@
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -6717,6 +6717,31 @@
"secretName": {
"type": "string",
"description": "Name of the secret in the pod's namespace to use. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets"
},
"items": {
"type": "array",
"items": {
"$ref": "v1.KeyToPath"
},
"description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the '..' path or start with '..'."
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
@ -7092,24 +7117,6 @@
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -17018,6 +17018,31 @@
"secretName": {
"type": "string",
"description": "Name of the secret in the pod's namespace to use. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets"
},
"items": {
"type": "array",
"items": {
"$ref": "v1.KeyToPath"
},
"description": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the '..' path or start with '..'."
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
@ -17103,24 +17128,6 @@
}
}
},
"v1.KeyToPath": {
"id": "v1.KeyToPath",
"description": "Maps a string key to a path within a volume.",
"required": [
"key",
"path"
],
"properties": {
"key": {
"type": "string",
"description": "The key to project."
},
"path": {
"type": "string",
"description": "The relative path of the file to map the key to. May not be an absolute path. May not contain the path element '..'. May not start with the string '..'."
}
}
},
"v1.Container": {
"id": "v1.Container",
"description": "A single application container that you want to run within a pod.",

View File

@ -2513,6 +2513,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<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">items</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the <em>..</em> path or start with <em>..</em>.</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"><a href="#_v1_keytopath">v1.KeyToPath</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -3979,7 +3986,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-06 14:18:34 UTC
Last updated 2016-05-18 12:33:08 UTC
</div>
</div>
</body>

View File

@ -2303,6 +2303,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i
<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">items</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the <em>..</em> path or start with <em>..</em>.</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"><a href="#_v1_keytopath">v1.KeyToPath</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -5969,7 +5976,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-10 20:28:46 UTC
Last updated 2016-05-18 12:32:45 UTC
</div>
</div>
</body>

View File

@ -2848,6 +2848,13 @@ The resulting set of endpoints can be viewed as:<br>
<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">items</p></td>
<td class="tableblock halign-left valign-top"><p class="tableblock">If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the <em>..</em> path or start with <em>..</em>.</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"><a href="#_v1_keytopath">v1.KeyToPath</a> array</p></td>
<td class="tableblock halign-left valign-top"></td>
</tr>
</tbody>
</table>
@ -7860,7 +7867,7 @@ The resulting set of endpoints can be viewed as:<br>
</div>
<div id="footer">
<div id="footer-text">
Last updated 2016-05-10 14:28:49 UTC
Last updated 2016-05-18 12:32:13 UTC
</div>
</div>
</body>

View File

@ -2748,6 +2748,17 @@ func DeepCopy_api_SecretList(in SecretList, out *SecretList, c *conversion.Clone
func DeepCopy_api_SecretVolumeSource(in SecretVolumeSource, out *SecretVolumeSource, c *conversion.Cloner) error {
out.SecretName = in.SecretName
if in.Items != nil {
in, out := in.Items, &out.Items
*out = make([]KeyToPath, len(in))
for i := range in {
if err := DeepCopy_api_KeyToPath(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}

View File

@ -10912,13 +10912,14 @@ func (x *SecretVolumeSource) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [1]bool
var yyq2 [2]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[0] = x.SecretName != ""
yyq2[1] = len(x.Items) != 0
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(1)
r.EncodeArrayStart(2)
} else {
yynn2 = 0
for _, b := range yyq2 {
@ -10954,6 +10955,39 @@ func (x *SecretVolumeSource) CodecEncodeSelf(e *codec1978.Encoder) {
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[1] {
if x.Items == nil {
r.EncodeNil()
} else {
yym7 := z.EncBinary()
_ = yym7
if false {
} else {
h.encSliceKeyToPath(([]KeyToPath)(x.Items), e)
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[1] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("items"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.Items == nil {
r.EncodeNil()
} else {
yym8 := z.EncBinary()
_ = yym8
if false {
} else {
h.encSliceKeyToPath(([]KeyToPath)(x.Items), e)
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
@ -11021,6 +11055,18 @@ func (x *SecretVolumeSource) codecDecodeSelfFromMap(l int, d *codec1978.Decoder)
} else {
x.SecretName = string(r.DecodeString())
}
case "items":
if r.TryDecodeAsNil() {
x.Items = nil
} else {
yyv5 := &x.Items
yym6 := z.DecBinary()
_ = yym6
if false {
} else {
h.decSliceKeyToPath((*[]KeyToPath)(yyv5), d)
}
}
default:
z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3
@ -11032,16 +11078,16 @@ func (x *SecretVolumeSource) codecDecodeSelfFromArray(l int, d *codec1978.Decode
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj5 int
var yyb5 bool
var yyhl5 bool = l >= 0
yyj5++
if yyhl5 {
yyb5 = yyj5 > l
var yyj7 int
var yyb7 bool
var yyhl7 bool = l >= 0
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb5 = r.CheckBreak()
yyb7 = r.CheckBreak()
}
if yyb5 {
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -11051,18 +11097,40 @@ func (x *SecretVolumeSource) codecDecodeSelfFromArray(l int, d *codec1978.Decode
} else {
x.SecretName = string(r.DecodeString())
}
for {
yyj5++
if yyhl5 {
yyb5 = yyj5 > l
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb5 = r.CheckBreak()
yyb7 = r.CheckBreak()
}
if yyb5 {
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Items = nil
} else {
yyv9 := &x.Items
yym10 := z.DecBinary()
_ = yym10
if false {
} else {
h.decSliceKeyToPath((*[]KeyToPath)(yyv9), d)
}
}
for {
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj5-1, "")
z.DecStructFieldNotFound(yyj7-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}
@ -52261,125 +52329,6 @@ func (x codecSelfer1234) decSlicePersistentVolumeClaim(v *[]PersistentVolumeClai
}
}
func (x codecSelfer1234) encSliceDownwardAPIVolumeFile(v []DownwardAPIVolumeFile, e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
r.EncodeArrayStart(len(v))
for _, yyv1 := range v {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy2 := &yyv1
yy2.CodecEncodeSelf(e)
}
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x codecSelfer1234) decSliceDownwardAPIVolumeFile(v *[]DownwardAPIVolumeFile, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yyv1 := *v
yyh1, yyl1 := z.DecSliceHelperStart()
var yyc1 bool
_ = yyc1
if yyl1 == 0 {
if yyv1 == nil {
yyv1 = []DownwardAPIVolumeFile{}
yyc1 = true
} else if len(yyv1) != 0 {
yyv1 = yyv1[:0]
yyc1 = true
}
} else if yyl1 > 0 {
var yyrr1, yyrl1 int
var yyrt1 bool
_, _ = yyrl1, yyrt1
yyrr1 = yyl1 // len(yyv1)
if yyl1 > cap(yyv1) {
yyrg1 := len(yyv1) > 0
yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 48)
if yyrt1 {
if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1]
} else {
yyv1 = make([]DownwardAPIVolumeFile, yyrl1)
}
} else {
yyv1 = make([]DownwardAPIVolumeFile, yyrl1)
}
yyc1 = true
yyrr1 = len(yyv1)
if yyrg1 {
copy(yyv1, yyv21)
}
} else if yyl1 != len(yyv1) {
yyv1 = yyv1[:yyl1]
yyc1 = true
}
yyj1 := 0
for ; yyj1 < yyrr1; yyj1++ {
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = DownwardAPIVolumeFile{}
} else {
yyv2 := &yyv1[yyj1]
yyv2.CodecDecodeSelf(d)
}
}
if yyrt1 {
for ; yyj1 < yyl1; yyj1++ {
yyv1 = append(yyv1, DownwardAPIVolumeFile{})
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = DownwardAPIVolumeFile{}
} else {
yyv3 := &yyv1[yyj1]
yyv3.CodecDecodeSelf(d)
}
}
}
} else {
yyj1 := 0
for ; !r.CheckBreak(); yyj1++ {
if yyj1 >= len(yyv1) {
yyv1 = append(yyv1, DownwardAPIVolumeFile{}) // var yyz1 DownwardAPIVolumeFile
yyc1 = true
}
yyh1.ElemContainerState(yyj1)
if yyj1 < len(yyv1) {
if r.TryDecodeAsNil() {
yyv1[yyj1] = DownwardAPIVolumeFile{}
} else {
yyv4 := &yyv1[yyj1]
yyv4.CodecDecodeSelf(d)
}
} else {
z.DecSwallow()
}
}
if yyj1 < len(yyv1) {
yyv1 = yyv1[:yyj1]
yyc1 = true
} else if yyj1 == 0 && yyv1 == nil {
yyv1 = []DownwardAPIVolumeFile{}
yyc1 = true
}
}
yyh1.End()
if yyc1 {
*v = yyv1
}
}
func (x codecSelfer1234) encSliceKeyToPath(v []KeyToPath, e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
@ -52499,6 +52448,125 @@ func (x codecSelfer1234) decSliceKeyToPath(v *[]KeyToPath, d *codec1978.Decoder)
}
}
func (x codecSelfer1234) encSliceDownwardAPIVolumeFile(v []DownwardAPIVolumeFile, e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)
_, _, _ = h, z, r
r.EncodeArrayStart(len(v))
for _, yyv1 := range v {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
yy2 := &yyv1
yy2.CodecEncodeSelf(e)
}
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
}
func (x codecSelfer1234) decSliceDownwardAPIVolumeFile(v *[]DownwardAPIVolumeFile, d *codec1978.Decoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
yyv1 := *v
yyh1, yyl1 := z.DecSliceHelperStart()
var yyc1 bool
_ = yyc1
if yyl1 == 0 {
if yyv1 == nil {
yyv1 = []DownwardAPIVolumeFile{}
yyc1 = true
} else if len(yyv1) != 0 {
yyv1 = yyv1[:0]
yyc1 = true
}
} else if yyl1 > 0 {
var yyrr1, yyrl1 int
var yyrt1 bool
_, _ = yyrl1, yyrt1
yyrr1 = yyl1 // len(yyv1)
if yyl1 > cap(yyv1) {
yyrg1 := len(yyv1) > 0
yyv21 := yyv1
yyrl1, yyrt1 = z.DecInferLen(yyl1, z.DecBasicHandle().MaxInitLen, 48)
if yyrt1 {
if yyrl1 <= cap(yyv1) {
yyv1 = yyv1[:yyrl1]
} else {
yyv1 = make([]DownwardAPIVolumeFile, yyrl1)
}
} else {
yyv1 = make([]DownwardAPIVolumeFile, yyrl1)
}
yyc1 = true
yyrr1 = len(yyv1)
if yyrg1 {
copy(yyv1, yyv21)
}
} else if yyl1 != len(yyv1) {
yyv1 = yyv1[:yyl1]
yyc1 = true
}
yyj1 := 0
for ; yyj1 < yyrr1; yyj1++ {
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = DownwardAPIVolumeFile{}
} else {
yyv2 := &yyv1[yyj1]
yyv2.CodecDecodeSelf(d)
}
}
if yyrt1 {
for ; yyj1 < yyl1; yyj1++ {
yyv1 = append(yyv1, DownwardAPIVolumeFile{})
yyh1.ElemContainerState(yyj1)
if r.TryDecodeAsNil() {
yyv1[yyj1] = DownwardAPIVolumeFile{}
} else {
yyv3 := &yyv1[yyj1]
yyv3.CodecDecodeSelf(d)
}
}
}
} else {
yyj1 := 0
for ; !r.CheckBreak(); yyj1++ {
if yyj1 >= len(yyv1) {
yyv1 = append(yyv1, DownwardAPIVolumeFile{}) // var yyz1 DownwardAPIVolumeFile
yyc1 = true
}
yyh1.ElemContainerState(yyj1)
if yyj1 < len(yyv1) {
if r.TryDecodeAsNil() {
yyv1[yyj1] = DownwardAPIVolumeFile{}
} else {
yyv4 := &yyv1[yyj1]
yyv4.CodecDecodeSelf(d)
}
} else {
z.DecSwallow()
}
}
if yyj1 < len(yyv1) {
yyv1 = yyv1[:yyj1]
yyc1 = true
} else if yyj1 == 0 && yyv1 == nil {
yyv1 = []DownwardAPIVolumeFile{}
yyc1 = true
}
}
yyh1.End()
if yyc1 {
*v = yyv1
}
}
func (x codecSelfer1234) encSliceHTTPHeader(v []HTTPHeader, e *codec1978.Encoder) {
var h codecSelfer1234
z, r := codec1978.GenHelperEncoder(e)

View File

@ -586,6 +586,14 @@ type GitRepoVolumeSource struct {
type SecretVolumeSource struct {
// Name of the secret in the pod's namespace to use.
SecretName string `json:"secretName,omitempty"`
// If unspecified, each key-value pair in the Data field of the referenced
// Secret will be projected into the volume as a file whose name is the
// key and content is the value. If specified, the listed keys will be
// projected into the specified paths, and unlisted keys will not be
// present. If a key is specified which is not present in the Secret,
// the volume setup will error. Paths must be relative and may not contain
// the '..' path or start with '..'.
Items []KeyToPath `json:"items,omitempty"`
}
// Represents an NFS mount that lasts the lifetime of a pod.

View File

@ -6019,6 +6019,17 @@ func Convert_api_SecretList_To_v1_SecretList(in *api.SecretList, out *SecretList
func autoConvert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *SecretVolumeSource, out *api.SecretVolumeSource, s conversion.Scope) error {
out.SecretName = in.SecretName
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]api.KeyToPath, len(*in))
for i := range *in {
if err := Convert_v1_KeyToPath_To_api_KeyToPath(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}
@ -6028,6 +6039,17 @@ func Convert_v1_SecretVolumeSource_To_api_SecretVolumeSource(in *SecretVolumeSou
func autoConvert_api_SecretVolumeSource_To_v1_SecretVolumeSource(in *api.SecretVolumeSource, out *SecretVolumeSource, s conversion.Scope) error {
out.SecretName = in.SecretName
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]KeyToPath, len(*in))
for i := range *in {
if err := Convert_api_KeyToPath_To_v1_KeyToPath(&(*in)[i], &(*out)[i], s); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}

View File

@ -2702,6 +2702,17 @@ func DeepCopy_v1_SecretList(in SecretList, out *SecretList, c *conversion.Cloner
func DeepCopy_v1_SecretVolumeSource(in SecretVolumeSource, out *SecretVolumeSource, c *conversion.Cloner) error {
out.SecretName = in.SecretName
if in.Items != nil {
in, out := in.Items, &out.Items
*out = make([]KeyToPath, len(in))
for i := range in {
if err := DeepCopy_v1_KeyToPath(in[i], &(*out)[i], c); err != nil {
return err
}
}
} else {
out.Items = nil
}
return nil
}

View File

@ -6646,6 +6646,18 @@ func (m *SecretVolumeSource) MarshalTo(data []byte) (int, error) {
i++
i = encodeVarintGenerated(data, i, uint64(len(m.SecretName)))
i += copy(data[i:], m.SecretName)
if len(m.Items) > 0 {
for _, msg := range m.Items {
data[i] = 0x12
i++
i = encodeVarintGenerated(data, i, uint64(msg.Size()))
n, err := msg.MarshalTo(data[i:])
if err != nil {
return 0, err
}
i += n
}
}
return i, nil
}
@ -9625,6 +9637,12 @@ func (m *SecretVolumeSource) Size() (n int) {
_ = l
l = len(m.SecretName)
n += 1 + l + sovGenerated(uint64(l))
if len(m.Items) > 0 {
for _, e := range m.Items {
l = e.Size()
n += 1 + l + sovGenerated(uint64(l))
}
}
return n
}
@ -30659,6 +30677,37 @@ func (m *SecretVolumeSource) Unmarshal(data []byte) error {
}
m.SecretName = string(data[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Items", wireType)
}
var msglen int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowGenerated
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := data[iNdEx]
iNdEx++
msglen |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
if msglen < 0 {
return ErrInvalidLengthGenerated
}
postIndex := iNdEx + msglen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Items = append(m.Items, KeyToPath{})
if err := m.Items[len(m.Items)-1].Unmarshal(data[iNdEx:postIndex]); err != nil {
return err
}
iNdEx = postIndex
default:
iNdEx = preIndex
skippy, err := skipGenerated(data[iNdEx:])

View File

@ -2464,6 +2464,15 @@ message SecretVolumeSource {
// Name of the secret in the pod's namespace to use.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets
optional string secretName = 1;
// If unspecified, each key-value pair in the Data field of the referenced
// Secret will be projected into the volume as a file whose name is the
// key and content is the value. If specified, the listed keys will be
// projected into the specified paths, and unlisted keys will not be
// present. If a key is specified which is not present in the Secret,
// the volume setup will error. Paths must be relative and may not contain
// the '..' path or start with '..'.
repeated KeyToPath items = 2;
}
// SecurityContext holds security configuration that will be applied to a container.

View File

@ -11749,13 +11749,14 @@ func (x *SecretVolumeSource) CodecEncodeSelf(e *codec1978.Encoder) {
} else {
yysep2 := !z.EncBinary()
yy2arr2 := z.EncBasicHandle().StructToArray
var yyq2 [1]bool
var yyq2 [2]bool
_, _, _ = yysep2, yyq2, yy2arr2
const yyr2 bool = false
yyq2[0] = x.SecretName != ""
yyq2[1] = len(x.Items) != 0
var yynn2 int
if yyr2 || yy2arr2 {
r.EncodeArrayStart(1)
r.EncodeArrayStart(2)
} else {
yynn2 = 0
for _, b := range yyq2 {
@ -11791,6 +11792,39 @@ func (x *SecretVolumeSource) CodecEncodeSelf(e *codec1978.Encoder) {
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayElem1234)
if yyq2[1] {
if x.Items == nil {
r.EncodeNil()
} else {
yym7 := z.EncBinary()
_ = yym7
if false {
} else {
h.encSliceKeyToPath(([]KeyToPath)(x.Items), e)
}
}
} else {
r.EncodeNil()
}
} else {
if yyq2[1] {
z.EncSendContainerState(codecSelfer_containerMapKey1234)
r.EncodeString(codecSelferC_UTF81234, string("items"))
z.EncSendContainerState(codecSelfer_containerMapValue1234)
if x.Items == nil {
r.EncodeNil()
} else {
yym8 := z.EncBinary()
_ = yym8
if false {
} else {
h.encSliceKeyToPath(([]KeyToPath)(x.Items), e)
}
}
}
}
if yyr2 || yy2arr2 {
z.EncSendContainerState(codecSelfer_containerArrayEnd1234)
} else {
@ -11858,6 +11892,18 @@ func (x *SecretVolumeSource) codecDecodeSelfFromMap(l int, d *codec1978.Decoder)
} else {
x.SecretName = string(r.DecodeString())
}
case "items":
if r.TryDecodeAsNil() {
x.Items = nil
} else {
yyv5 := &x.Items
yym6 := z.DecBinary()
_ = yym6
if false {
} else {
h.decSliceKeyToPath((*[]KeyToPath)(yyv5), d)
}
}
default:
z.DecStructFieldNotFound(-1, yys3)
} // end switch yys3
@ -11869,16 +11915,16 @@ func (x *SecretVolumeSource) codecDecodeSelfFromArray(l int, d *codec1978.Decode
var h codecSelfer1234
z, r := codec1978.GenHelperDecoder(d)
_, _, _ = h, z, r
var yyj5 int
var yyb5 bool
var yyhl5 bool = l >= 0
yyj5++
if yyhl5 {
yyb5 = yyj5 > l
var yyj7 int
var yyb7 bool
var yyhl7 bool = l >= 0
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb5 = r.CheckBreak()
yyb7 = r.CheckBreak()
}
if yyb5 {
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
@ -11888,18 +11934,40 @@ func (x *SecretVolumeSource) codecDecodeSelfFromArray(l int, d *codec1978.Decode
} else {
x.SecretName = string(r.DecodeString())
}
for {
yyj5++
if yyhl5 {
yyb5 = yyj5 > l
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb5 = r.CheckBreak()
yyb7 = r.CheckBreak()
}
if yyb5 {
if yyb7 {
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
return
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
if r.TryDecodeAsNil() {
x.Items = nil
} else {
yyv9 := &x.Items
yym10 := z.DecBinary()
_ = yym10
if false {
} else {
h.decSliceKeyToPath((*[]KeyToPath)(yyv9), d)
}
}
for {
yyj7++
if yyhl7 {
yyb7 = yyj7 > l
} else {
yyb7 = r.CheckBreak()
}
if yyb7 {
break
}
z.DecSendContainerState(codecSelfer_containerArrayElem1234)
z.DecStructFieldNotFound(yyj5-1, "")
z.DecStructFieldNotFound(yyj7-1, "")
}
z.DecSendContainerState(codecSelfer_containerArrayEnd1234)
}

View File

@ -753,6 +753,14 @@ type SecretVolumeSource struct {
// Name of the secret in the pod's namespace to use.
// More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets
SecretName string `json:"secretName,omitempty" protobuf:"bytes,1,opt,name=secretName"`
// If unspecified, each key-value pair in the Data field of the referenced
// Secret will be projected into the volume as a file whose name is the
// key and content is the value. If specified, the listed keys will be
// projected into the specified paths, and unlisted keys will not be
// present. If a key is specified which is not present in the Secret,
// the volume setup will error. Paths must be relative and may not contain
// the '..' path or start with '..'.
Items []KeyToPath `json:"items,omitempty" protobuf:"bytes,2,rep,name=items"`
}
// Represents an NFS mount that lasts the lifetime of a pod.

View File

@ -1489,6 +1489,7 @@ func (SecretList) SwaggerDoc() map[string]string {
var map_SecretVolumeSource = map[string]string{
"": "Adapts a Secret into a volume.\n\nThe contents of the target Secret's Data field will be presented in a volume as files using the keys in the Data field as the file names. Secret volumes support ownership management and SELinux relabeling.",
"secretName": "Name of the secret in the pod's namespace to use. More info: http://releases.k8s.io/HEAD/docs/user-guide/volumes.md#secrets",
"items": "If unspecified, each key-value pair in the Data field of the referenced Secret will be projected into the volume as a file whose name is the key and content is the value. If specified, the listed keys will be projected into the specified paths, and unlisted keys will not be present. If a key is specified which is not present in the Secret, the volume setup will error. Paths must be relative and may not contain the '..' path or start with '..'.",
}
func (SecretVolumeSource) SwaggerDoc() map[string]string {

View File

@ -18,8 +18,6 @@ package secret
import (
"fmt"
"os"
"path"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
@ -74,7 +72,7 @@ func (plugin *secretPlugin) NewMounter(spec *volume.Spec, pod *api.Pod, opts vol
plugin.host.GetWriter(),
volume.NewCachedMetrics(volume.NewMetricsDu(getPathFromHost(plugin.host, pod.UID, spec.Name()))),
},
secretName: spec.Volume.Secret.SecretName,
source: *spec.Volume.Secret,
pod: *pod,
opts: &opts,
}, nil
@ -117,7 +115,7 @@ func getPathFromHost(host volume.VolumeHost, podUID types.UID, volName string) s
type secretVolumeMounter struct {
*secretVolume
secretName string
source api.SecretVolumeSource
pod api.Pod
opts *volume.VolumeOptions
}
@ -135,24 +133,7 @@ func (b *secretVolumeMounter) SetUp(fsGroup *int64) error {
return b.SetUpAt(b.GetPath(), fsGroup)
}
func (b *secretVolumeMounter) getMetaDir() string {
return path.Join(b.plugin.host.GetPodPluginDir(b.podUID, strings.EscapeQualifiedNameForDisk(secretPluginName)), b.volName)
}
func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
notMnt, err := b.mounter.IsLikelyNotMountPoint(dir)
// Getting an os.IsNotExist err from is a contingency; the directory
// may not exist yet, in which case, setup should run.
if err != nil && !os.IsNotExist(err) {
return err
}
// If the plugin readiness file is present for this volume and
// the setup dir is a mountpoint, this volume is already ready.
if volumeutil.IsReady(b.getMetaDir()) && !notMnt {
return nil
}
glog.V(3).Infof("Setting up volume %v for pod %v at %v", b.volName, b.pod.UID, dir)
// Wrap EmptyDir, let it do the setup.
@ -169,36 +150,68 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
return fmt.Errorf("Cannot setup secret volume %v because kube client is not configured", b.volName)
}
secret, err := kubeClient.Core().Secrets(b.pod.Namespace).Get(b.secretName)
secret, err := kubeClient.Core().Secrets(b.pod.Namespace).Get(b.source.SecretName)
if err != nil {
glog.Errorf("Couldn't get secret %v/%v", b.pod.Namespace, b.secretName)
glog.Errorf("Couldn't get secret %v/%v", b.pod.Namespace, b.source.SecretName)
return err
} else {
}
totalBytes := totalSecretBytes(secret)
glog.V(3).Infof("Received secret %v/%v containing (%v) pieces of data, %v total bytes",
b.pod.Namespace,
b.secretName,
b.source.SecretName,
len(secret.Data),
totalBytes)
}
for name, data := range secret.Data {
hostFilePath := path.Join(dir, name)
glog.V(3).Infof("Writing secret data %v/%v/%v (%v bytes) to host file %v", b.pod.Namespace, b.secretName, name, len(data), hostFilePath)
err := b.writer.WriteFile(hostFilePath, data, 0444)
payload, err := makePayload(b.source.Items, secret)
if err != nil {
glog.Errorf("Error writing secret data to host path: %v, %v", hostFilePath, err)
return err
}
writerContext := fmt.Sprintf("pod %v/%v volume %v", b.pod.Namespace, b.pod.Name, b.volName)
writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
if err != nil {
glog.Errorf("Error creating atomic writer: %v", err)
return err
}
volume.SetVolumeOwnership(b, fsGroup)
err = writer.Write(payload)
if err != nil {
glog.Errorf("Error writing payload to dir: %v", err)
return err
}
volumeutil.SetReady(b.getMetaDir())
err = volume.SetVolumeOwnership(b, fsGroup)
if err != nil {
glog.Errorf("Error applying volume ownership settings for group: %v", fsGroup)
return err
}
return nil
}
func makePayload(mappings []api.KeyToPath, secret *api.Secret) (map[string][]byte, error) {
payload := make(map[string][]byte, len(secret.Data))
if len(mappings) == 0 {
for name, data := range secret.Data {
payload[name] = []byte(data)
}
} else {
for _, ktp := range mappings {
content, ok := secret.Data[ktp.Key]
if !ok {
glog.Errorf("references non-existent secret key")
return nil, fmt.Errorf("references non-existent secret key")
}
payload[ktp.Path] = []byte(content)
}
}
return payload, nil
}
func totalSecretBytes(secret *api.Secret) int {
totalSize := 0
for _, bytes := range secret.Data {

View File

@ -21,6 +21,7 @@ import (
"io/ioutil"
"os"
"path"
"reflect"
"runtime"
"strings"
"testing"
@ -29,7 +30,6 @@ import (
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
"k8s.io/kubernetes/pkg/types"
"k8s.io/kubernetes/pkg/util/mount"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/empty_dir"
volumetest "k8s.io/kubernetes/pkg/volume/testing"
@ -38,6 +38,149 @@ import (
"github.com/stretchr/testify/assert"
)
func TestMakePayload(t *testing.T) {
cases := []struct {
name string
mappings []api.KeyToPath
secret *api.Secret
payload map[string][]byte
success bool
}{
{
name: "no overrides",
secret: &api.Secret{
Data: map[string][]byte{
"foo": []byte("foo"),
"bar": []byte("bar"),
},
},
payload: map[string][]byte{
"foo": []byte("foo"),
"bar": []byte("bar"),
},
success: true,
},
{
name: "basic 1",
mappings: []api.KeyToPath{
{
Key: "foo",
Path: "path/to/foo.txt",
},
},
secret: &api.Secret{
Data: map[string][]byte{
"foo": []byte("foo"),
"bar": []byte("bar"),
},
},
payload: map[string][]byte{
"path/to/foo.txt": []byte("foo"),
},
success: true,
},
{
name: "subdirs",
mappings: []api.KeyToPath{
{
Key: "foo",
Path: "path/to/1/2/3/foo.txt",
},
},
secret: &api.Secret{
Data: map[string][]byte{
"foo": []byte("foo"),
"bar": []byte("bar"),
},
},
payload: map[string][]byte{
"path/to/1/2/3/foo.txt": []byte("foo"),
},
success: true,
},
{
name: "subdirs 2",
mappings: []api.KeyToPath{
{
Key: "foo",
Path: "path/to/1/2/3/foo.txt",
},
},
secret: &api.Secret{
Data: map[string][]byte{
"foo": []byte("foo"),
"bar": []byte("bar"),
},
},
payload: map[string][]byte{
"path/to/1/2/3/foo.txt": []byte("foo"),
},
success: true,
},
{
name: "subdirs 3",
mappings: []api.KeyToPath{
{
Key: "foo",
Path: "path/to/1/2/3/foo.txt",
},
{
Key: "bar",
Path: "another/path/to/the/esteemed/bar.bin",
},
},
secret: &api.Secret{
Data: map[string][]byte{
"foo": []byte("foo"),
"bar": []byte("bar"),
},
},
payload: map[string][]byte{
"path/to/1/2/3/foo.txt": []byte("foo"),
"another/path/to/the/esteemed/bar.bin": []byte("bar"),
},
success: true,
},
{
name: "non existent key",
mappings: []api.KeyToPath{
{
Key: "zab",
Path: "path/to/foo.txt",
},
},
secret: &api.Secret{
Data: map[string][]byte{
"foo": []byte("foo"),
"bar": []byte("bar"),
},
},
success: false,
},
}
for _, tc := range cases {
actualPayload, err := makePayload(tc.mappings, tc.secret)
if err != nil && tc.success {
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
continue
}
if err == nil && !tc.success {
t.Errorf("%v: unexpected success making payload", tc.name)
continue
}
if !tc.success {
continue
}
if e, a := tc.payload, actualPayload; !reflect.DeepEqual(e, a) {
t.Errorf("%v: expected and actual payload do not match", tc.name)
}
}
}
func newTestHost(t *testing.T, clientset clientset.Interface) (string, volume.VolumeHost) {
tempDir, err := ioutil.TempDir("/tmp", "secret_volume_test.")
if err != nil {
@ -137,66 +280,6 @@ func TestPlugin(t *testing.T) {
}
}
// Test the case where the 'ready' file has been created and the pod volume dir
// is a mountpoint. Mount should not be called.
func TestPluginIdempotent(t *testing.T) {
var (
testPodUID = types.UID("test_pod_uid2")
testVolumeName = "test_volume_name"
testNamespace = "test_secret_namespace"
testName = "test_secret_name"
volumeSpec = volumeSpec(testVolumeName, testName)
secret = secret(testNamespace, testName)
client = fake.NewSimpleClientset(&secret)
pluginMgr = volume.VolumePluginMgr{}
rootDir, host = newTestHost(t, client)
)
pluginMgr.InitPlugins(ProbeVolumePlugins(), host)
plugin, err := pluginMgr.FindPluginByName(secretPluginName)
if err != nil {
t.Errorf("Can't find the plugin by name")
}
podVolumeDir := fmt.Sprintf("%v/pods/test_pod_uid2/volumes/kubernetes.io~secret/test_volume_name", rootDir)
podMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid2/plugins/kubernetes.io~secret/test_volume_name", rootDir)
pod := &api.Pod{ObjectMeta: api.ObjectMeta{UID: testPodUID}}
physicalMounter := host.GetMounter().(*mount.FakeMounter)
physicalMounter.MountPoints = []mount.MountPoint{
{
Path: podVolumeDir,
},
}
util.SetReady(podMetadataDir)
mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{})
if err != nil {
t.Errorf("Failed to make a new Mounter: %v", err)
}
if mounter == nil {
t.Errorf("Got a nil Mounter")
}
volumePath := mounter.GetPath()
err = mounter.SetUp(nil)
if err != nil {
t.Errorf("Failed to setup volume: %v", err)
}
if len(physicalMounter.Log) != 0 {
t.Errorf("Unexpected calls made to physicalMounter: %v", physicalMounter.Log)
}
if _, err := os.Stat(volumePath); err != nil {
if !os.IsNotExist(err) {
t.Errorf("SetUp() failed unexpectedly: %v", err)
}
} else {
t.Errorf("volume path should not exist: %v", volumePath)
}
}
// Test the case where the plugin's ready file exists, but the volume dir is not a
// mountpoint, which is the state the system will be in after reboot. The dir
// should be mounter and the secret data written to it.

View File

@ -76,7 +76,7 @@ var _ = framework.KubeDescribe("Secrets", func() {
Containers: []api.Container{
{
Name: "secret-volume-test",
Image: "gcr.io/google_containers/mounttest:0.2",
Image: "gcr.io/google_containers/mounttest:0.7",
Args: []string{
"--file_content=/etc/secret-volume/data-1",
"--file_mode=/etc/secret-volume/data-1"},
@ -95,7 +95,7 @@ var _ = framework.KubeDescribe("Secrets", func() {
framework.TestContainerOutput("consume secrets", f.Client, pod, 0, []string{
"content of file \"/etc/secret-volume/data-1\": value-1",
"mode of file \"/etc/secret-volume/data-1\": -r--r--r--",
"mode of file \"/etc/secret-volume/data-1\": -rw-r--r--",
}, f.Namespace.Name)
})