diff --git a/api/swagger-spec/apps_v1alpha1.json b/api/swagger-spec/apps_v1alpha1.json index 1abfb9a44e2..33e5ca084ae 100644 --- a/api/swagger-spec/apps_v1alpha1.json +++ b/api/swagger-spec/apps_v1alpha1.json @@ -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.", diff --git a/api/swagger-spec/batch_v1.json b/api/swagger-spec/batch_v1.json index de114104c82..3a208512861 100644 --- a/api/swagger-spec/batch_v1.json +++ b/api/swagger-spec/batch_v1.json @@ -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.", diff --git a/api/swagger-spec/extensions_v1beta1.json b/api/swagger-spec/extensions_v1beta1.json index 7a6ab88f53b..ffcbbb9ce73 100644 --- a/api/swagger-spec/extensions_v1beta1.json +++ b/api/swagger-spec/extensions_v1beta1.json @@ -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.", diff --git a/api/swagger-spec/v1.json b/api/swagger-spec/v1.json index 3c04fcdde75..46ee7c53088 100644 --- a/api/swagger-spec/v1.json +++ b/api/swagger-spec/v1.json @@ -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.", diff --git a/docs/api-reference/batch/v1/definitions.html b/docs/api-reference/batch/v1/definitions.html index 45d52586215..1c3668794c2 100755 --- a/docs/api-reference/batch/v1/definitions.html +++ b/docs/api-reference/batch/v1/definitions.html @@ -2513,6 +2513,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i

string

+ +

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

+

false

+

v1.KeyToPath array

+ + @@ -3979,7 +3986,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 c5e4b2dc72e..c5609ba6832 100755 --- a/docs/api-reference/extensions/v1beta1/definitions.html +++ b/docs/api-reference/extensions/v1beta1/definitions.html @@ -2303,6 +2303,13 @@ Populated by the system when a graceful deletion is requested. Read-only. More i

string

+ +

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

+

false

+

v1.KeyToPath array

+ + @@ -5969,7 +5976,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 5ba4bbbe0ca..71da264dfcd 100755 --- a/docs/api-reference/v1/definitions.html +++ b/docs/api-reference/v1/definitions.html @@ -2848,6 +2848,13 @@ The resulting set of endpoints can be viewed as:

string

+ +

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

+

false

+

v1.KeyToPath array

+ + @@ -7860,7 +7867,7 @@ The resulting set of endpoints can be viewed as:
diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index 152c26808fb..7227bda2d3b 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -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 } diff --git a/pkg/api/types.generated.go b/pkg/api/types.generated.go index 0e46d2c4fe5..39b592f87ea 100644 --- a/pkg/api/types.generated.go +++ b/pkg/api/types.generated.go @@ -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 { + yyb7 = r.CheckBreak() + } + 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 { - yyb5 = r.CheckBreak() + h.decSliceKeyToPath((*[]KeyToPath)(yyv9), d) } - if yyb5 { + } + 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) diff --git a/pkg/api/types.go b/pkg/api/types.go index cff7d647d1a..22615384d76 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -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. diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index bdd4bafab8d..5f0734e8a0b 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -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 } diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index 15939d98ce5..a2c5bd5cb43 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -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 } diff --git a/pkg/api/v1/generated.pb.go b/pkg/api/v1/generated.pb.go index 01c2113ab12..4f9603ac249 100644 --- a/pkg/api/v1/generated.pb.go +++ b/pkg/api/v1/generated.pb.go @@ -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:]) diff --git a/pkg/api/v1/generated.proto b/pkg/api/v1/generated.proto index a821388b6bc..52abd618a8d 100644 --- a/pkg/api/v1/generated.proto +++ b/pkg/api/v1/generated.proto @@ -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. diff --git a/pkg/api/v1/types.generated.go b/pkg/api/v1/types.generated.go index 7b9d953f7c5..998856e5cce 100644 --- a/pkg/api/v1/types.generated.go +++ b/pkg/api/v1/types.generated.go @@ -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 { + yyb7 = r.CheckBreak() + } + 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 { - yyb5 = r.CheckBreak() + h.decSliceKeyToPath((*[]KeyToPath)(yyv9), d) } - if yyb5 { + } + 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) } diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index e3c51a56a19..3c6f8c01821 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -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. diff --git a/pkg/api/v1/types_swagger_doc_generated.go b/pkg/api/v1/types_swagger_doc_generated.go index 8410b13f116..0b00f79d51e 100644 --- a/pkg/api/v1/types_swagger_doc_generated.go +++ b/pkg/api/v1/types_swagger_doc_generated.go @@ -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 { diff --git a/pkg/volume/secret/secret.go b/pkg/volume/secret/secret.go index 4573441181d..7a003c4795c 100644 --- a/pkg/volume/secret/secret.go +++ b/pkg/volume/secret/secret.go @@ -18,8 +18,6 @@ package secret import ( "fmt" - "os" - "path" "github.com/golang/glog" "k8s.io/kubernetes/pkg/api" @@ -74,9 +72,9 @@ 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, - pod: *pod, - opts: &opts, + source: *spec.Volume.Secret, + pod: *pod, + opts: &opts, }, nil } @@ -117,9 +115,9 @@ func getPathFromHost(host volume.VolumeHost, podUID types.UID, volName string) s type secretVolumeMounter struct { *secretVolume - secretName string - pod api.Pod - opts *volume.VolumeOptions + source api.SecretVolumeSource + pod api.Pod + opts *volume.VolumeOptions } var _ volume.Mounter = &secretVolumeMounter{} @@ -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,34 +150,66 @@ 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, - 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) - if err != nil { - glog.Errorf("Error writing secret data to host path: %v, %v", hostFilePath, err) - return err + totalBytes := totalSecretBytes(secret) + glog.V(3).Infof("Received secret %v/%v containing (%v) pieces of data, %v total bytes", + b.pod.Namespace, + b.source.SecretName, + len(secret.Data), + totalBytes) + + payload, err := makePayload(b.source.Items, secret) + if err != nil { + 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 + } + + err = writer.Write(payload) + if err != nil { + glog.Errorf("Error writing payload to dir: %v", err) + return err + } + + 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) } } - volume.SetVolumeOwnership(b, fsGroup) - - volumeutil.SetReady(b.getMetaDir()) - - return nil + return payload, nil } func totalSecretBytes(secret *api.Secret) int { diff --git a/pkg/volume/secret/secret_test.go b/pkg/volume/secret/secret_test.go index c5471ff13e2..74604b1e820 100644 --- a/pkg/volume/secret/secret_test.go +++ b/pkg/volume/secret/secret_test.go @@ -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. diff --git a/test/e2e/secrets.go b/test/e2e/secrets.go index dcbf3ac189e..ea46413ee56 100644 --- a/test/e2e/secrets.go +++ b/test/e2e/secrets.go @@ -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) })