mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-06 16:06:51 +00:00
Implements projected volume driver
Proposal: kubernetes/kubernetes#35313
This commit is contained in:
@@ -267,6 +267,16 @@ func coreFuncs(t apitesting.TestingCommon) []interface{} {
|
||||
mode &= 0777
|
||||
d.DefaultMode = &mode
|
||||
},
|
||||
func(s *api.ProjectedVolumeSource, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(s) // fuzz self without calling this function again
|
||||
|
||||
// DefaultMode should always be set, it has a default
|
||||
// value and it is expected to be between 0 and 0777
|
||||
var mode int32
|
||||
c.Fuzz(&mode)
|
||||
mode &= 0777
|
||||
s.DefaultMode = &mode
|
||||
},
|
||||
func(k *api.KeyToPath, c fuzz.Continue) {
|
||||
c.FuzzNoCustom(k) // fuzz self without calling this function again
|
||||
k.Key = c.RandString()
|
||||
|
||||
@@ -294,6 +294,8 @@ type VolumeSource struct {
|
||||
AzureDisk *AzureDiskVolumeSource
|
||||
// PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine
|
||||
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource
|
||||
// Items for all in one resources secrets, configmaps, and downward API
|
||||
Projected *ProjectedVolumeSource
|
||||
}
|
||||
|
||||
// Similar to VolumeSource but meant for the administrator who creates PVs.
|
||||
@@ -746,7 +748,29 @@ type SecretVolumeSource struct {
|
||||
// mode, like fsGroup, and the result can be other mode bits set.
|
||||
// +optional
|
||||
DefaultMode *int32
|
||||
// Specify whether the Secret or it's key must be defined
|
||||
// Specify whether the Secret or its key must be defined
|
||||
// +optional
|
||||
Optional *bool
|
||||
}
|
||||
|
||||
// Adapts a secret into a projected volume.
|
||||
//
|
||||
// The contents of the target Secret's Data field will be presented in a
|
||||
// projected volume as files using the keys in the Data field as the file names.
|
||||
// Note that this is identical to a secret volume source without the default
|
||||
// mode.
|
||||
type SecretProjection struct {
|
||||
LocalObjectReference
|
||||
// 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 unless it is marked optional. Paths must be
|
||||
// relative and may not contain the '..' path or start with '..'.
|
||||
// +optional
|
||||
Items []KeyToPath
|
||||
// Specify whether the Secret or its key must be defined
|
||||
// +optional
|
||||
Optional *bool
|
||||
}
|
||||
@@ -927,6 +951,15 @@ type DownwardAPIVolumeFile struct {
|
||||
Mode *int32
|
||||
}
|
||||
|
||||
// Represents downward API info for projecting into a projected volume.
|
||||
// Note that this is identical to a downwardAPI volume source without the default
|
||||
// mode.
|
||||
type DownwardAPIProjection struct {
|
||||
// Items is a list of DownwardAPIVolume file
|
||||
// +optional
|
||||
Items []DownwardAPIVolumeFile
|
||||
}
|
||||
|
||||
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
|
||||
type AzureFileVolumeSource struct {
|
||||
// the name of secret that contains Azure Storage Account Name and Key
|
||||
@@ -1017,6 +1050,54 @@ type ConfigMapVolumeSource struct {
|
||||
Optional *bool
|
||||
}
|
||||
|
||||
// Adapts a ConfigMap into a projected volume.
|
||||
//
|
||||
// The contents of the target ConfigMap's Data field will be presented in a
|
||||
// projected volume as files using the keys in the Data field as the file names,
|
||||
// unless the items element is populated with specific mappings of keys to paths.
|
||||
// Note that this is identical to a configmap volume source without the default
|
||||
// mode.
|
||||
type ConfigMapProjection struct {
|
||||
LocalObjectReference
|
||||
// If unspecified, each key-value pair in the Data field of the referenced
|
||||
// ConfigMap 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 ConfigMap,
|
||||
// the volume setup will error unless it is marked optional. Paths must be
|
||||
// relative and may not contain the '..' path or start with '..'.
|
||||
// +optional
|
||||
Items []KeyToPath
|
||||
// Specify whether the ConfigMap or it's keys must be defined
|
||||
// +optional
|
||||
Optional *bool
|
||||
}
|
||||
|
||||
// Represents a projected volume source
|
||||
type ProjectedVolumeSource struct {
|
||||
// list of volume projections
|
||||
Sources []VolumeProjection
|
||||
// Mode bits to use on created files by default. Must be a value between
|
||||
// 0 and 0777.
|
||||
// Directories within the path are not affected by this setting.
|
||||
// This might be in conflict with other options that affect the file
|
||||
// mode, like fsGroup, and the result can be other mode bits set.
|
||||
// +optional
|
||||
DefaultMode *int32
|
||||
}
|
||||
|
||||
// Projection that may be projected along with other supported volume types
|
||||
type VolumeProjection struct {
|
||||
// all types below are the supported types for projection into the same volume
|
||||
|
||||
// information about the secret data to project
|
||||
Secret *SecretProjection
|
||||
// information about the downwardAPI data to project
|
||||
DownwardAPI *DownwardAPIProjection
|
||||
// information about the configMap data to project
|
||||
ConfigMap *ConfigMapProjection
|
||||
}
|
||||
|
||||
// Maps a string key to a path within a volume.
|
||||
type KeyToPath struct {
|
||||
// The key to project.
|
||||
|
||||
@@ -39,6 +39,7 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||
SetDefaults_SecretVolumeSource,
|
||||
SetDefaults_ConfigMapVolumeSource,
|
||||
SetDefaults_DownwardAPIVolumeSource,
|
||||
SetDefaults_ProjectedVolumeSource,
|
||||
SetDefaults_Secret,
|
||||
SetDefaults_PersistentVolume,
|
||||
SetDefaults_PersistentVolumeClaim,
|
||||
@@ -218,6 +219,12 @@ func SetDefaults_Secret(obj *Secret) {
|
||||
obj.Type = SecretTypeOpaque
|
||||
}
|
||||
}
|
||||
func SetDefaults_ProjectedVolumeSource(obj *ProjectedVolumeSource) {
|
||||
if obj.DefaultMode == nil {
|
||||
perm := int32(ProjectedVolumeSourceDefaultMode)
|
||||
obj.DefaultMode = &perm
|
||||
}
|
||||
}
|
||||
func SetDefaults_PersistentVolume(obj *PersistentVolume) {
|
||||
if obj.Status.Phase == "" {
|
||||
obj.Status.Phase = VolumePending
|
||||
|
||||
@@ -376,6 +376,28 @@ func TestSetDefaultDownwardAPIVolumeSource(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultProjectedVolumeSource(t *testing.T) {
|
||||
s := v1.PodSpec{}
|
||||
s.Volumes = []v1.Volume{
|
||||
{
|
||||
VolumeSource: v1.VolumeSource{
|
||||
Projected: &v1.ProjectedVolumeSource{},
|
||||
},
|
||||
},
|
||||
}
|
||||
pod := &v1.Pod{
|
||||
Spec: s,
|
||||
}
|
||||
output := roundTrip(t, runtime.Object(pod))
|
||||
pod2 := output.(*v1.Pod)
|
||||
defaultMode := pod2.Spec.Volumes[0].VolumeSource.Projected.DefaultMode
|
||||
expectedMode := v1.ProjectedVolumeSourceDefaultMode
|
||||
|
||||
if defaultMode == nil || *defaultMode != expectedMode {
|
||||
t.Errorf("Expected ProjectedVolumeSource DefaultMode %v, got %v", expectedMode, defaultMode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetDefaultSecret(t *testing.T) {
|
||||
s := &v1.Secret{}
|
||||
obj2 := roundTrip(t, runtime.Object(s))
|
||||
|
||||
@@ -326,6 +326,8 @@ type VolumeSource struct {
|
||||
AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,22,opt,name=azureDisk"`
|
||||
// PhotonPersistentDisk represents a PhotonController persistent disk attached and mounted on kubelets host machine
|
||||
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty" protobuf:"bytes,23,opt,name=photonPersistentDisk"`
|
||||
// Items for all in one resources secrets, configmaps, and downward API
|
||||
Projected *ProjectedVolumeSource `json:"projected,omitempty"`
|
||||
}
|
||||
|
||||
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
|
||||
@@ -944,6 +946,28 @@ const (
|
||||
SecretVolumeSourceDefaultMode int32 = 0644
|
||||
)
|
||||
|
||||
// Adapts a secret into a projected volume.
|
||||
//
|
||||
// The contents of the target Secret's Data field will be presented in a
|
||||
// projected volume as files using the keys in the Data field as the file names.
|
||||
// Note that this is identical to a secret volume source without the default
|
||||
// mode.
|
||||
type SecretProjection struct {
|
||||
LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"`
|
||||
// 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 unless it is marked optional. Paths must be
|
||||
// relative and may not contain the '..' path or start with '..'.
|
||||
// +optional
|
||||
Items []KeyToPath `json:"items,omitempty" protobuf:"bytes,2,rep,name=items"`
|
||||
// Specify whether the Secret or its key must be defined
|
||||
// +optional
|
||||
Optional *bool `json:"optional,omitempty" protobuf:"varint,4,opt,name=optional"`
|
||||
}
|
||||
|
||||
// Represents an NFS mount that lasts the lifetime of a pod.
|
||||
// NFS volumes do not support ownership management or SELinux relabeling.
|
||||
type NFSVolumeSource struct {
|
||||
@@ -1108,6 +1132,58 @@ const (
|
||||
ConfigMapVolumeSourceDefaultMode int32 = 0644
|
||||
)
|
||||
|
||||
// Adapts a ConfigMap into a projected volume.
|
||||
//
|
||||
// The contents of the target ConfigMap's Data field will be presented in a
|
||||
// projected volume as files using the keys in the Data field as the file names,
|
||||
// unless the items element is populated with specific mappings of keys to paths.
|
||||
// Note that this is identical to a configmap volume source without the default
|
||||
// mode.
|
||||
type ConfigMapProjection struct {
|
||||
LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"`
|
||||
// If unspecified, each key-value pair in the Data field of the referenced
|
||||
// ConfigMap 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 ConfigMap,
|
||||
// the volume setup will error unless it is marked optional. Paths must be
|
||||
// relative and may not contain the '..' path or start with '..'.
|
||||
// +optional
|
||||
Items []KeyToPath `json:"items,omitempty" protobuf:"bytes,2,rep,name=items"`
|
||||
// Specify whether the ConfigMap or it's keys must be defined
|
||||
// +optional
|
||||
Optional *bool `json:"optional,omitempty" protobuf:"varint,4,opt,name=optional"`
|
||||
}
|
||||
|
||||
// Represents a projected volume source
|
||||
type ProjectedVolumeSource struct {
|
||||
// list of volume projections
|
||||
Sources []VolumeProjection `json:"sources"`
|
||||
// Mode bits to use on created files by default. Must be a value between
|
||||
// 0 and 0777.
|
||||
// Directories within the path are not affected by this setting.
|
||||
// This might be in conflict with other options that affect the file
|
||||
// mode, like fsGroup, and the result can be other mode bits set.
|
||||
// +optional
|
||||
DefaultMode *int32 `json:"defaultMode,omitempty"`
|
||||
}
|
||||
|
||||
// Projection that may be projected along with other supported volume types
|
||||
type VolumeProjection struct {
|
||||
// all types below are the supported types for projection into the same volume
|
||||
|
||||
// information about the secret data to project
|
||||
Secret *SecretProjection `json:"secret,omitempty"`
|
||||
// information about the downwardAPI data to project
|
||||
DownwardAPI *DownwardAPIProjection `json:"downwardAPI,omitempty"`
|
||||
// information about the configMap data to project
|
||||
ConfigMap *ConfigMapProjection `json:"configMap,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
ProjectedVolumeSourceDefaultMode int32 = 0644
|
||||
)
|
||||
|
||||
// Maps a string key to a path within a volume.
|
||||
type KeyToPath struct {
|
||||
// The key to project.
|
||||
@@ -4095,6 +4171,15 @@ type DownwardAPIVolumeFile struct {
|
||||
Mode *int32 `json:"mode,omitempty" protobuf:"varint,4,opt,name=mode"`
|
||||
}
|
||||
|
||||
// Represents downward API info for projecting into a projected volume.
|
||||
// Note that this is identical to a downwardAPI volume source without the default
|
||||
// mode.
|
||||
type DownwardAPIProjection struct {
|
||||
// Items is a list of DownwardAPIVolume file
|
||||
// +optional
|
||||
Items []DownwardAPIVolumeFile `json:"items,omitempty" protobuf:"bytes,1,rep,name=items"`
|
||||
}
|
||||
|
||||
// SecurityContext holds security configuration that will be applied to a container.
|
||||
// Some fields are present in both SecurityContext and PodSecurityContext. When both
|
||||
// are set, the values in SecurityContext take precedence.
|
||||
|
||||
@@ -518,6 +518,14 @@ func validateVolumeSource(source *api.VolumeSource, fldPath *field.Path) field.E
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...)
|
||||
}
|
||||
if source.Projected != nil {
|
||||
if numVolumes > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("projected"), "may not specify more than 1 volume type"))
|
||||
} else {
|
||||
numVolumes++
|
||||
allErrs = append(allErrs, validateProjectedVolumeSource(source.Projected, fldPath.Child("projected"))...)
|
||||
}
|
||||
}
|
||||
|
||||
if numVolumes == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
|
||||
@@ -723,6 +731,30 @@ var validDownwardAPIFieldPathExpressions = sets.NewString(
|
||||
"metadata.labels",
|
||||
"metadata.annotations")
|
||||
|
||||
func validateDownwardAPIVolumeFile(file *api.DownwardAPIVolumeFile, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
if len(file.Path) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
|
||||
}
|
||||
allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...)
|
||||
if file.FieldRef != nil {
|
||||
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
||||
if file.ResourceFieldRef != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
|
||||
}
|
||||
} else if file.ResourceFieldRef != nil {
|
||||
allErrs = append(allErrs, validateContainerResourceFieldSelector(file.ResourceFieldRef, &validContainerResourceFieldPathExpressions, fldPath.Child("resourceFieldRef"), true)...)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required"))
|
||||
}
|
||||
if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, volumeModeErrorMsg))
|
||||
}
|
||||
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
@@ -732,27 +764,99 @@ func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSou
|
||||
}
|
||||
|
||||
for _, file := range downwardAPIVolume.Items {
|
||||
if len(file.Path) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
|
||||
}
|
||||
allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...)
|
||||
if file.FieldRef != nil {
|
||||
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
||||
if file.ResourceFieldRef != nil {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
|
||||
allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath)...)
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateProjectionSources(projection *api.ProjectedVolumeSource, projectionMode *int32, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
allPaths := sets.String{}
|
||||
|
||||
for _, source := range projection.Sources {
|
||||
numSources := 0
|
||||
if source.Secret != nil {
|
||||
if numSources > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("secret"), "may not specify more than 1 volume type"))
|
||||
} else {
|
||||
numSources++
|
||||
if len(source.Secret.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
}
|
||||
itemsPath := fldPath.Child("items")
|
||||
for i, kp := range source.Secret.Items {
|
||||
itemPath := itemsPath.Index(i)
|
||||
allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...)
|
||||
if len(kp.Path) > 0 {
|
||||
curPath := kp.Path
|
||||
if !allPaths.Has(curPath) {
|
||||
allPaths.Insert(curPath)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, source.Secret.Name, "conflicting duplicate paths"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if file.ResourceFieldRef != nil {
|
||||
allErrs = append(allErrs, validateContainerResourceFieldSelector(file.ResourceFieldRef, &validContainerResourceFieldPathExpressions, fldPath.Child("resourceFieldRef"), true)...)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Required(fldPath, "one of fieldRef and resourceFieldRef is required"))
|
||||
}
|
||||
if file.Mode != nil && (*file.Mode > 0777 || *file.Mode < 0) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, volumeModeErrorMsg))
|
||||
if source.ConfigMap != nil {
|
||||
if numSources > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("configMap"), "may not specify more than 1 volume type"))
|
||||
} else {
|
||||
numSources++
|
||||
if len(source.ConfigMap.Name) == 0 {
|
||||
allErrs = append(allErrs, field.Required(fldPath.Child("name"), ""))
|
||||
}
|
||||
itemsPath := fldPath.Child("items")
|
||||
for i, kp := range source.ConfigMap.Items {
|
||||
itemPath := itemsPath.Index(i)
|
||||
allErrs = append(allErrs, validateKeyToPath(&kp, itemPath)...)
|
||||
if len(kp.Path) > 0 {
|
||||
curPath := kp.Path
|
||||
if !allPaths.Has(curPath) {
|
||||
allPaths.Insert(curPath)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, source.ConfigMap.Name, "conflicting duplicate paths"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if source.DownwardAPI != nil {
|
||||
if numSources > 0 {
|
||||
allErrs = append(allErrs, field.Forbidden(fldPath.Child("downwardAPI"), "may not specify more than 1 volume type"))
|
||||
} else {
|
||||
numSources++
|
||||
for _, file := range source.DownwardAPI.Items {
|
||||
allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath.Child("downwardAPI"))...)
|
||||
if len(file.Path) > 0 {
|
||||
curPath := file.Path
|
||||
if !allPaths.Has(curPath) {
|
||||
allPaths.Insert(curPath)
|
||||
} else {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath, curPath, "conflicting duplicate paths"))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return allErrs
|
||||
}
|
||||
|
||||
func validateProjectedVolumeSource(projection *api.ProjectedVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||
allErrs := field.ErrorList{}
|
||||
|
||||
projectionMode := projection.DefaultMode
|
||||
if projectionMode != nil && (*projectionMode > 0777 || *projectionMode < 0) {
|
||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("defaultMode"), *projectionMode, volumeModeErrorMsg))
|
||||
}
|
||||
|
||||
allErrs = append(allErrs, validateProjectionSources(projection, projectionMode, fldPath)...)
|
||||
return allErrs
|
||||
}
|
||||
|
||||
// This validate will make sure targetPath:
|
||||
// 1. is not abs path
|
||||
// 2. does not have any element which is ".."
|
||||
|
||||
Reference in New Issue
Block a user