Implements projected volume driver

Proposal: kubernetes/kubernetes#35313
pull/6/head
Jeff Peeler 2016-11-10 17:33:06 -05:00
parent b385a94fed
commit 8fb1b71c66
20 changed files with 3269 additions and 29 deletions

View File

@ -46,6 +46,7 @@ import (
"k8s.io/kubernetes/pkg/volume/iscsi"
"k8s.io/kubernetes/pkg/volume/nfs"
"k8s.io/kubernetes/pkg/volume/photon_pd"
"k8s.io/kubernetes/pkg/volume/projected"
"k8s.io/kubernetes/pkg/volume/quobyte"
"k8s.io/kubernetes/pkg/volume/rbd"
"k8s.io/kubernetes/pkg/volume/secret"
@ -88,6 +89,7 @@ func ProbeVolumePlugins(pluginDir string) []volume.VolumePlugin {
allPlugins = append(allPlugins, vsphere_volume.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...)
return allPlugins
}

View File

@ -243,6 +243,7 @@ pkg/util/yaml
pkg/version/prometheus
pkg/volume
pkg/volume/downwardapi
pkg/volume/projected
pkg/volume/quobyte
pkg/volume/util/nestedpendingoperations
pkg/volume/util/operationexecutor

View File

@ -147,6 +147,14 @@ test/e2e/common/host_path.go: fmt.Sprintf("--file_content_in_loop=%v", filePat
test/e2e/common/host_path.go: fmt.Sprintf("--file_content_in_loop=%v", filePathInReader),
test/e2e/common/host_path.go: fmt.Sprintf("--retry_time=%d", retryDuration),
test/e2e/common/host_path.go: fmt.Sprintf("--retry_time=%d", retryDuration),
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/projected-configmap-volume/data-1"},
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/projected-configmap-volumes/create/data-1"},
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/projected-configmap-volumes/delete/data-1"},
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/projected-configmap-volumes/update/data-3"},
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/projected-secret-volumes/create/data-1"},
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/projected-secret-volumes/delete/data-1"},
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/projected-secret-volumes/update/data-3"},
test/e2e/common/projected.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=" + filePath},
test/e2e/common/secrets.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/secret-volumes/create/data-1"},
test/e2e/common/secrets.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/secret-volumes/delete/data-1"},
test/e2e/common/secrets.go: Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/secret-volumes/update/data-3"},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -905,6 +905,7 @@ var (
Quobyte FSType = "quobyte"
AzureDisk FSType = "azureDisk"
PhotonPersistentDisk FSType = "photonPersistentDisk"
Projected FSType = "projected"
All FSType = "*"
)

View File

@ -279,6 +279,12 @@ func getSecretNames(pod *v1.Pod) sets.String {
for i := range pod.Spec.Volumes {
if source := pod.Spec.Volumes[i].Secret; source != nil {
result.Insert(source.SecretName)
} else if source := pod.Spec.Volumes[i].Projected; source != nil {
for j := range source.Sources {
if secretVolumeSource := source.Sources[j].Secret; secretVolumeSource != nil {
result.Insert(secretVolumeSource.Name)
}
}
}
}
return result

View File

@ -61,7 +61,9 @@ func GetAllFSTypesAsSet() sets.String {
string(extensions.VsphereVolume),
string(extensions.Quobyte),
string(extensions.AzureDisk),
string(extensions.PhotonPersistentDisk))
string(extensions.PhotonPersistentDisk),
string(extensions.Projected),
)
return fstypes
}
@ -114,6 +116,8 @@ func GetVolumeFSType(v api.Volume) (extensions.FSType, error) {
return extensions.AzureDisk, nil
case v.PhotonPersistentDisk != nil:
return extensions.PhotonPersistentDisk, nil
case v.Projected != nil:
return extensions.Projected, nil
}
return "", fmt.Errorf("unknown volume type for volume: %#v", v)

View File

@ -193,7 +193,7 @@ func (b *configMapVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
len(configMap.Data),
totalBytes)
payload, err := makePayload(b.source.Items, configMap, b.source.DefaultMode, optional)
payload, err := MakePayload(b.source.Items, configMap, b.source.DefaultMode, optional)
if err != nil {
return err
}
@ -220,7 +220,8 @@ func (b *configMapVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
return nil
}
func makePayload(mappings []v1.KeyToPath, configMap *v1.ConfigMap, defaultMode *int32, optional bool) (map[string]volumeutil.FileProjection, error) {
// Note: this function is exported so that it can be called from the projection volume driver
func MakePayload(mappings []v1.KeyToPath, configMap *v1.ConfigMap, defaultMode *int32, optional bool) (map[string]volumeutil.FileProjection, error) {
if defaultMode == nil {
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
}

View File

@ -238,7 +238,7 @@ func TestMakePayload(t *testing.T) {
}
for _, tc := range cases {
actualPayload, err := makePayload(tc.mappings, tc.configMap, &tc.mode, tc.optional)
actualPayload, err := MakePayload(tc.mappings, tc.configMap, &tc.mode, tc.optional)
if err != nil && tc.success {
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
continue

View File

@ -175,7 +175,7 @@ func (b *downwardAPIVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
return err
}
data, err := b.collectData(b.source.DefaultMode)
data, err := CollectData(b.source.Items, b.pod, b.plugin.host, b.source.DefaultMode)
if err != nil {
glog.Errorf("Error preparing data for downwardAPI volume %v for pod %v/%v: %s", b.volName, b.pod.Namespace, b.pod.Name, err.Error())
return err
@ -203,17 +203,19 @@ func (b *downwardAPIVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
return nil
}
// collectData collects requested downwardAPI in data map.
// CollectData collects requested downwardAPI in data map.
// Map's key is the requested name of file to dump
// Map's value is the (sorted) content of the field to be dumped in the file.
func (d *downwardAPIVolume) collectData(defaultMode *int32) (map[string]volumeutil.FileProjection, error) {
//
// Note: this function is exported so that it can be called from the projection volume driver
func CollectData(items []v1.DownwardAPIVolumeFile, pod *v1.Pod, host volume.VolumeHost, defaultMode *int32) (map[string]volumeutil.FileProjection, error) {
if defaultMode == nil {
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
}
errlist := []error{}
data := make(map[string]volumeutil.FileProjection)
for _, fileInfo := range d.items {
for _, fileInfo := range items {
var fileProjection volumeutil.FileProjection
fPath := path.Clean(fileInfo.Path)
if fileInfo.Mode != nil {
@ -223,7 +225,7 @@ func (d *downwardAPIVolume) collectData(defaultMode *int32) (map[string]volumeut
}
if fileInfo.FieldRef != nil {
// TODO: unify with Kubelet.podFieldSelectorRuntimeValue
if values, err := fieldpath.ExtractFieldPathAsString(d.pod, fileInfo.FieldRef.FieldPath); err != nil {
if values, err := fieldpath.ExtractFieldPathAsString(pod, fileInfo.FieldRef.FieldPath); err != nil {
glog.Errorf("Unable to extract field %s: %s", fileInfo.FieldRef.FieldPath, err.Error())
errlist = append(errlist, err)
} else {
@ -231,10 +233,10 @@ func (d *downwardAPIVolume) collectData(defaultMode *int32) (map[string]volumeut
}
} else if fileInfo.ResourceFieldRef != nil {
containerName := fileInfo.ResourceFieldRef.ContainerName
nodeAllocatable, err := d.plugin.host.GetNodeAllocatable()
nodeAllocatable, err := host.GetNodeAllocatable()
if err != nil {
errlist = append(errlist, err)
} else if values, err := fieldpath.ExtractResourceValueByContainerNameAndNodeAllocatable(fileInfo.ResourceFieldRef, d.pod, containerName, nodeAllocatable); err != nil {
} else if values, err := fieldpath.ExtractResourceValueByContainerNameAndNodeAllocatable(fileInfo.ResourceFieldRef, pod, containerName, nodeAllocatable); err != nil {
glog.Errorf("Unable to extract field %s: %s", fileInfo.ResourceFieldRef.Resource, err.Error())
errlist = append(errlist, err)
} else {

View File

@ -0,0 +1,325 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package projected
import (
"fmt"
"sort"
"strings"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/pkg/api/v1"
utilstrings "k8s.io/kubernetes/pkg/util/strings"
"k8s.io/kubernetes/pkg/volume"
"k8s.io/kubernetes/pkg/volume/configmap"
"k8s.io/kubernetes/pkg/volume/downwardapi"
"k8s.io/kubernetes/pkg/volume/secret"
volumeutil "k8s.io/kubernetes/pkg/volume/util"
)
// ProbeVolumePlugins is the entry point for plugin detection in a package.
func ProbeVolumePlugins() []volume.VolumePlugin {
return []volume.VolumePlugin{&projectedPlugin{}}
}
const (
projectedPluginName = "kubernetes.io/projected"
)
type projectedPlugin struct {
host volume.VolumeHost
getSecret func(namespace, name string) (*v1.Secret, error)
}
var _ volume.VolumePlugin = &projectedPlugin{}
func wrappedVolumeSpec() volume.Spec {
return volume.Spec{
Volume: &v1.Volume{
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{Medium: v1.StorageMediumMemory},
},
},
}
}
func getPath(uid types.UID, volName string, host volume.VolumeHost) string {
return host.GetPodVolumeDir(uid, utilstrings.EscapeQualifiedNameForDisk(projectedPluginName), volName)
}
func (plugin *projectedPlugin) Init(host volume.VolumeHost) error {
plugin.host = host
plugin.getSecret = host.GetSecretFunc()
return nil
}
func (plugin *projectedPlugin) GetPluginName() string {
return projectedPluginName
}
func (plugin *projectedPlugin) GetVolumeName(spec *volume.Spec) (string, error) {
_, _, err := getVolumeSource(spec)
if err != nil {
return "", err
}
return spec.Name(), nil
}
func (plugin *projectedPlugin) CanSupport(spec *volume.Spec) bool {
return spec.Volume != nil && spec.Volume.Projected != nil
}
func (plugin *projectedPlugin) RequiresRemount() bool {
return true
}
func (plugin *projectedPlugin) NewMounter(spec *volume.Spec, pod *v1.Pod, opts volume.VolumeOptions) (volume.Mounter, error) {
return &projectedVolumeMounter{
projectedVolume: &projectedVolume{
volName: spec.Name(),
sources: spec.Volume.Projected.Sources,
podUID: pod.UID,
plugin: plugin,
},
source: *spec.Volume.Projected,
pod: pod,
opts: &opts,
}, nil
}
func (plugin *projectedPlugin) NewUnmounter(volName string, podUID types.UID) (volume.Unmounter, error) {
return &projectedVolumeUnmounter{
&projectedVolume{
volName: volName,
podUID: podUID,
plugin: plugin,
},
}, nil
}
func (plugin *projectedPlugin) ConstructVolumeSpec(volumeName, mountPath string) (*volume.Spec, error) {
projectedVolume := &v1.Volume{
Name: volumeName,
VolumeSource: v1.VolumeSource{
Projected: &v1.ProjectedVolumeSource{},
},
}
return volume.NewSpecFromVolume(projectedVolume), nil
}
type projectedVolume struct {
volName string
sources []v1.VolumeProjection
podUID types.UID
plugin *projectedPlugin
volume.MetricsNil
}
var _ volume.Volume = &projectedVolume{}
func (sv *projectedVolume) GetPath() string {
return getPath(sv.podUID, sv.volName, sv.plugin.host)
}
type projectedVolumeMounter struct {
*projectedVolume
source v1.ProjectedVolumeSource
pod *v1.Pod
opts *volume.VolumeOptions
}
var _ volume.Mounter = &projectedVolumeMounter{}
func (sv *projectedVolume) GetAttributes() volume.Attributes {
return volume.Attributes{
ReadOnly: true,
Managed: true,
SupportsSELinux: true,
}
}
// Checks prior to mount operations to verify that the required components (binaries, etc.)
// to mount the volume are available on the underlying node.
// If not, it returns an error
func (s *projectedVolumeMounter) CanMount() error {
return nil
}
func (s *projectedVolumeMounter) SetUp(fsGroup *int64) error {
return s.SetUpAt(s.GetPath(), fsGroup)
}
func (s *projectedVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
glog.V(3).Infof("Setting up volume %v for pod %v at %v", s.volName, s.pod.UID, dir)
wrapped, err := s.plugin.host.NewWrapperMounter(s.volName, wrappedVolumeSpec(), s.pod, *s.opts)
if err != nil {
return err
}
if err := wrapped.SetUpAt(dir, fsGroup); err != nil {
return err
}
data, err := s.collectData()
if err != nil {
glog.Errorf("Error preparing data for projected volume %v for pod %v/%v: %s", s.volName, s.pod.Namespace, s.pod.Name, err.Error())
}
writerContext := fmt.Sprintf("pod %v/%v volume %v", s.pod.Namespace, s.pod.Name, s.volName)
writer, err := volumeutil.NewAtomicWriter(dir, writerContext)
if err != nil {
glog.Errorf("Error creating atomic writer: %v", err)
return err
}
err = writer.Write(data)
if err != nil {
glog.Errorf("Error writing payload to dir: %v", err)
return err
}
err = volume.SetVolumeOwnership(s, fsGroup)
if err != nil {
glog.Errorf("Error applying volume ownership settings for group: %v", fsGroup)
return err
}
return nil
}
func (s *projectedVolumeMounter) collectData() (map[string]volumeutil.FileProjection, error) {
if s.source.DefaultMode == nil {
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
}
kubeClient := s.plugin.host.GetKubeClient()
if kubeClient == nil {
return nil, fmt.Errorf("Cannot setup projected volume %v because kube client is not configured", s.volName)
}
errlist := []error{}
payload := make(map[string]volumeutil.FileProjection)
for _, source := range s.source.Sources {
if source.Secret != nil {
optional := source.Secret.Optional != nil && *source.Secret.Optional
secretapi, err := s.plugin.getSecret(s.pod.Namespace, source.Secret.Name)
if err != nil {
if !(errors.IsNotFound(err) && optional) {
glog.Errorf("Couldn't get secret %v/%v", s.pod.Namespace, source.Secret.Name)
errlist = append(errlist, err)
}
secretapi = &v1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: s.pod.Namespace,
Name: source.Secret.Name,
},
}
}
secretPayload, err := secret.MakePayload(source.Secret.Items, secretapi, s.source.DefaultMode, optional)
if err != nil {
glog.Errorf("Couldn't get secret %v/%v: %v", s.pod.Namespace, source.Secret.Name, err)
errlist = append(errlist, err)
continue
}
for k, v := range secretPayload {
payload[k] = v
}
} else if source.ConfigMap != nil {
optional := source.ConfigMap.Optional != nil && *source.ConfigMap.Optional
configMap, err := kubeClient.Core().ConfigMaps(s.pod.Namespace).Get(source.ConfigMap.Name, metav1.GetOptions{})
if err != nil {
if !(errors.IsNotFound(err) && optional) {
glog.Errorf("Couldn't get configMap %v/%v: %v", s.pod.Namespace, source.ConfigMap.Name, err)
errlist = append(errlist, err)
continue
}
configMap = &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Namespace: s.pod.Namespace,
Name: source.ConfigMap.Name,
},
}
}
configMapPayload, err := configmap.MakePayload(source.ConfigMap.Items, configMap, s.source.DefaultMode, optional)
if err != nil {
errlist = append(errlist, err)
continue
}
for k, v := range configMapPayload {
payload[k] = v
}
} else if source.DownwardAPI != nil {
downwardAPIPayload, err := downwardapi.CollectData(source.DownwardAPI.Items, s.pod, s.plugin.host, s.source.DefaultMode)
if err != nil {
errlist = append(errlist, err)
continue
}
for k, v := range downwardAPIPayload {
payload[k] = v
}
}
}
return payload, utilerrors.NewAggregate(errlist)
}
func sortLines(values string) string {
splitted := strings.Split(values, "\n")
sort.Strings(splitted)
return strings.Join(splitted, "\n")
}
type projectedVolumeUnmounter struct {
*projectedVolume
}
var _ volume.Unmounter = &projectedVolumeUnmounter{}
func (c *projectedVolumeUnmounter) TearDown() error {
return c.TearDownAt(c.GetPath())
}
func (c *projectedVolumeUnmounter) TearDownAt(dir string) error {
glog.V(3).Infof("Tearing down volume %v for pod %v at %v", c.volName, c.podUID, dir)
wrapped, err := c.plugin.host.NewWrapperUnmounter(c.volName, wrappedVolumeSpec(), c.podUID)
if err != nil {
return err
}
return wrapped.TearDownAt(dir)
}
func getVolumeSource(spec *volume.Spec) (*v1.ProjectedVolumeSource, bool, error) {
var readOnly bool
var volumeSource *v1.ProjectedVolumeSource
if spec.Volume != nil && spec.Volume.Projected != nil {
volumeSource = spec.Volume.Projected
readOnly = spec.ReadOnly
}
return volumeSource, readOnly, fmt.Errorf("Spec does not reference a projected volume type")
}

File diff suppressed because it is too large Load Diff

View File

@ -208,7 +208,7 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
len(secret.Data),
totalBytes)
payload, err := makePayload(b.source.Items, secret, b.source.DefaultMode, optional)
payload, err := MakePayload(b.source.Items, secret, b.source.DefaultMode, optional)
if err != nil {
return err
}
@ -235,7 +235,8 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
return nil
}
func makePayload(mappings []v1.KeyToPath, secret *v1.Secret, defaultMode *int32, optional bool) (map[string]volumeutil.FileProjection, error) {
// Note: this function is exported so that it can be called from the projection volume driver
func MakePayload(mappings []v1.KeyToPath, secret *v1.Secret, defaultMode *int32, optional bool) (map[string]volumeutil.FileProjection, error) {
if defaultMode == nil {
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
}

View File

@ -241,7 +241,7 @@ func TestMakePayload(t *testing.T) {
}
for _, tc := range cases {
actualPayload, err := makePayload(tc.mappings, tc.secret, &tc.mode, tc.optional)
actualPayload, err := MakePayload(tc.mappings, tc.secret, &tc.mode, tc.optional)
if err != nil && tc.success {
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
continue

1534
test/e2e/common/projected.go Normal file

File diff suppressed because it is too large Load Diff