mirror of https://github.com/k3s-io/k3s
parent
b385a94fed
commit
8fb1b71c66
|
@ -46,6 +46,7 @@ import (
|
||||||
"k8s.io/kubernetes/pkg/volume/iscsi"
|
"k8s.io/kubernetes/pkg/volume/iscsi"
|
||||||
"k8s.io/kubernetes/pkg/volume/nfs"
|
"k8s.io/kubernetes/pkg/volume/nfs"
|
||||||
"k8s.io/kubernetes/pkg/volume/photon_pd"
|
"k8s.io/kubernetes/pkg/volume/photon_pd"
|
||||||
|
"k8s.io/kubernetes/pkg/volume/projected"
|
||||||
"k8s.io/kubernetes/pkg/volume/quobyte"
|
"k8s.io/kubernetes/pkg/volume/quobyte"
|
||||||
"k8s.io/kubernetes/pkg/volume/rbd"
|
"k8s.io/kubernetes/pkg/volume/rbd"
|
||||||
"k8s.io/kubernetes/pkg/volume/secret"
|
"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, vsphere_volume.ProbeVolumePlugins()...)
|
||||||
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, azure_dd.ProbeVolumePlugins()...)
|
||||||
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
|
allPlugins = append(allPlugins, photon_pd.ProbeVolumePlugins()...)
|
||||||
|
allPlugins = append(allPlugins, projected.ProbeVolumePlugins()...)
|
||||||
return allPlugins
|
return allPlugins
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -243,6 +243,7 @@ pkg/util/yaml
|
||||||
pkg/version/prometheus
|
pkg/version/prometheus
|
||||||
pkg/volume
|
pkg/volume
|
||||||
pkg/volume/downwardapi
|
pkg/volume/downwardapi
|
||||||
|
pkg/volume/projected
|
||||||
pkg/volume/quobyte
|
pkg/volume/quobyte
|
||||||
pkg/volume/util/nestedpendingoperations
|
pkg/volume/util/nestedpendingoperations
|
||||||
pkg/volume/util/operationexecutor
|
pkg/volume/util/operationexecutor
|
||||||
|
|
|
@ -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("--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/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/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/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"},
|
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"},
|
||||||
|
|
|
@ -267,6 +267,16 @@ func coreFuncs(t apitesting.TestingCommon) []interface{} {
|
||||||
mode &= 0777
|
mode &= 0777
|
||||||
d.DefaultMode = &mode
|
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) {
|
func(k *api.KeyToPath, c fuzz.Continue) {
|
||||||
c.FuzzNoCustom(k) // fuzz self without calling this function again
|
c.FuzzNoCustom(k) // fuzz self without calling this function again
|
||||||
k.Key = c.RandString()
|
k.Key = c.RandString()
|
||||||
|
|
|
@ -294,6 +294,8 @@ type VolumeSource struct {
|
||||||
AzureDisk *AzureDiskVolumeSource
|
AzureDisk *AzureDiskVolumeSource
|
||||||
// PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine
|
// PhotonPersistentDisk represents a Photon Controller persistent disk attached and mounted on kubelets host machine
|
||||||
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource
|
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.
|
// 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.
|
// mode, like fsGroup, and the result can be other mode bits set.
|
||||||
// +optional
|
// +optional
|
||||||
DefaultMode *int32
|
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
|
||||||
Optional *bool
|
Optional *bool
|
||||||
}
|
}
|
||||||
|
@ -927,6 +951,15 @@ type DownwardAPIVolumeFile struct {
|
||||||
Mode *int32
|
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.
|
// AzureFile represents an Azure File Service mount on the host and bind mount to the pod.
|
||||||
type AzureFileVolumeSource struct {
|
type AzureFileVolumeSource struct {
|
||||||
// the name of secret that contains Azure Storage Account Name and Key
|
// the name of secret that contains Azure Storage Account Name and Key
|
||||||
|
@ -1017,6 +1050,54 @@ type ConfigMapVolumeSource struct {
|
||||||
Optional *bool
|
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.
|
// Maps a string key to a path within a volume.
|
||||||
type KeyToPath struct {
|
type KeyToPath struct {
|
||||||
// The key to project.
|
// The key to project.
|
||||||
|
|
|
@ -39,6 +39,7 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error {
|
||||||
SetDefaults_SecretVolumeSource,
|
SetDefaults_SecretVolumeSource,
|
||||||
SetDefaults_ConfigMapVolumeSource,
|
SetDefaults_ConfigMapVolumeSource,
|
||||||
SetDefaults_DownwardAPIVolumeSource,
|
SetDefaults_DownwardAPIVolumeSource,
|
||||||
|
SetDefaults_ProjectedVolumeSource,
|
||||||
SetDefaults_Secret,
|
SetDefaults_Secret,
|
||||||
SetDefaults_PersistentVolume,
|
SetDefaults_PersistentVolume,
|
||||||
SetDefaults_PersistentVolumeClaim,
|
SetDefaults_PersistentVolumeClaim,
|
||||||
|
@ -218,6 +219,12 @@ func SetDefaults_Secret(obj *Secret) {
|
||||||
obj.Type = SecretTypeOpaque
|
obj.Type = SecretTypeOpaque
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
func SetDefaults_ProjectedVolumeSource(obj *ProjectedVolumeSource) {
|
||||||
|
if obj.DefaultMode == nil {
|
||||||
|
perm := int32(ProjectedVolumeSourceDefaultMode)
|
||||||
|
obj.DefaultMode = &perm
|
||||||
|
}
|
||||||
|
}
|
||||||
func SetDefaults_PersistentVolume(obj *PersistentVolume) {
|
func SetDefaults_PersistentVolume(obj *PersistentVolume) {
|
||||||
if obj.Status.Phase == "" {
|
if obj.Status.Phase == "" {
|
||||||
obj.Status.Phase = VolumePending
|
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) {
|
func TestSetDefaultSecret(t *testing.T) {
|
||||||
s := &v1.Secret{}
|
s := &v1.Secret{}
|
||||||
obj2 := roundTrip(t, runtime.Object(s))
|
obj2 := roundTrip(t, runtime.Object(s))
|
||||||
|
|
|
@ -326,6 +326,8 @@ type VolumeSource struct {
|
||||||
AzureDisk *AzureDiskVolumeSource `json:"azureDisk,omitempty" protobuf:"bytes,22,opt,name=azureDisk"`
|
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 represents a PhotonController persistent disk attached and mounted on kubelets host machine
|
||||||
PhotonPersistentDisk *PhotonPersistentDiskVolumeSource `json:"photonPersistentDisk,omitempty" protobuf:"bytes,23,opt,name=photonPersistentDisk"`
|
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.
|
// PersistentVolumeClaimVolumeSource references the user's PVC in the same namespace.
|
||||||
|
@ -944,6 +946,28 @@ const (
|
||||||
SecretVolumeSourceDefaultMode int32 = 0644
|
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.
|
// Represents an NFS mount that lasts the lifetime of a pod.
|
||||||
// NFS volumes do not support ownership management or SELinux relabeling.
|
// NFS volumes do not support ownership management or SELinux relabeling.
|
||||||
type NFSVolumeSource struct {
|
type NFSVolumeSource struct {
|
||||||
|
@ -1108,6 +1132,58 @@ const (
|
||||||
ConfigMapVolumeSourceDefaultMode int32 = 0644
|
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.
|
// Maps a string key to a path within a volume.
|
||||||
type KeyToPath struct {
|
type KeyToPath struct {
|
||||||
// The key to project.
|
// The key to project.
|
||||||
|
@ -4095,6 +4171,15 @@ type DownwardAPIVolumeFile struct {
|
||||||
Mode *int32 `json:"mode,omitempty" protobuf:"varint,4,opt,name=mode"`
|
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.
|
// SecurityContext holds security configuration that will be applied to a container.
|
||||||
// Some fields are present in both SecurityContext and PodSecurityContext. When both
|
// Some fields are present in both SecurityContext and PodSecurityContext. When both
|
||||||
// are set, the values in SecurityContext take precedence.
|
// are set, the values in SecurityContext take precedence.
|
||||||
|
|
|
@ -518,6 +518,14 @@ func validateVolumeSource(source *api.VolumeSource, fldPath *field.Path) field.E
|
||||||
numVolumes++
|
numVolumes++
|
||||||
allErrs = append(allErrs, validateAzureDisk(source.AzureDisk, fldPath.Child("azureDisk"))...)
|
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 {
|
if numVolumes == 0 {
|
||||||
allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
|
allErrs = append(allErrs, field.Required(fldPath, "must specify a volume type"))
|
||||||
|
@ -723,6 +731,30 @@ var validDownwardAPIFieldPathExpressions = sets.NewString(
|
||||||
"metadata.labels",
|
"metadata.labels",
|
||||||
"metadata.annotations")
|
"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 {
|
func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSource, fldPath *field.Path) field.ErrorList {
|
||||||
allErrs := field.ErrorList{}
|
allErrs := field.ErrorList{}
|
||||||
|
|
||||||
|
@ -732,27 +764,99 @@ func validateDownwardAPIVolumeSource(downwardAPIVolume *api.DownwardAPIVolumeSou
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range downwardAPIVolume.Items {
|
for _, file := range downwardAPIVolume.Items {
|
||||||
if len(file.Path) == 0 {
|
allErrs = append(allErrs, validateDownwardAPIVolumeFile(&file, fldPath)...)
|
||||||
allErrs = append(allErrs, field.Required(fldPath.Child("path"), ""))
|
}
|
||||||
}
|
return allErrs
|
||||||
allErrs = append(allErrs, validateLocalNonReservedPath(file.Path, fldPath.Child("path"))...)
|
}
|
||||||
if file.FieldRef != nil {
|
|
||||||
allErrs = append(allErrs, validateObjectFieldSelector(file.FieldRef, &validDownwardAPIFieldPathExpressions, fldPath.Child("fieldRef"))...)
|
func validateProjectionSources(projection *api.ProjectedVolumeSource, projectionMode *int32, fldPath *field.Path) field.ErrorList {
|
||||||
if file.ResourceFieldRef != nil {
|
allErrs := field.ErrorList{}
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath, "resource", "fieldRef and resourceFieldRef can not be specified simultaneously"))
|
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) {
|
if source.ConfigMap != nil {
|
||||||
allErrs = append(allErrs, field.Invalid(fldPath.Child("mode"), *file.Mode, volumeModeErrorMsg))
|
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
|
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:
|
// This validate will make sure targetPath:
|
||||||
// 1. is not abs path
|
// 1. is not abs path
|
||||||
// 2. does not have any element which is ".."
|
// 2. does not have any element which is ".."
|
||||||
|
|
|
@ -905,6 +905,7 @@ var (
|
||||||
Quobyte FSType = "quobyte"
|
Quobyte FSType = "quobyte"
|
||||||
AzureDisk FSType = "azureDisk"
|
AzureDisk FSType = "azureDisk"
|
||||||
PhotonPersistentDisk FSType = "photonPersistentDisk"
|
PhotonPersistentDisk FSType = "photonPersistentDisk"
|
||||||
|
Projected FSType = "projected"
|
||||||
All FSType = "*"
|
All FSType = "*"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -279,6 +279,12 @@ func getSecretNames(pod *v1.Pod) sets.String {
|
||||||
for i := range pod.Spec.Volumes {
|
for i := range pod.Spec.Volumes {
|
||||||
if source := pod.Spec.Volumes[i].Secret; source != nil {
|
if source := pod.Spec.Volumes[i].Secret; source != nil {
|
||||||
result.Insert(source.SecretName)
|
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
|
return result
|
||||||
|
|
|
@ -61,7 +61,9 @@ func GetAllFSTypesAsSet() sets.String {
|
||||||
string(extensions.VsphereVolume),
|
string(extensions.VsphereVolume),
|
||||||
string(extensions.Quobyte),
|
string(extensions.Quobyte),
|
||||||
string(extensions.AzureDisk),
|
string(extensions.AzureDisk),
|
||||||
string(extensions.PhotonPersistentDisk))
|
string(extensions.PhotonPersistentDisk),
|
||||||
|
string(extensions.Projected),
|
||||||
|
)
|
||||||
return fstypes
|
return fstypes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,6 +116,8 @@ func GetVolumeFSType(v api.Volume) (extensions.FSType, error) {
|
||||||
return extensions.AzureDisk, nil
|
return extensions.AzureDisk, nil
|
||||||
case v.PhotonPersistentDisk != nil:
|
case v.PhotonPersistentDisk != nil:
|
||||||
return extensions.PhotonPersistentDisk, nil
|
return extensions.PhotonPersistentDisk, nil
|
||||||
|
case v.Projected != nil:
|
||||||
|
return extensions.Projected, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf("unknown volume type for volume: %#v", v)
|
return "", fmt.Errorf("unknown volume type for volume: %#v", v)
|
||||||
|
|
|
@ -193,7 +193,7 @@ func (b *configMapVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
len(configMap.Data),
|
len(configMap.Data),
|
||||||
totalBytes)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -220,7 +220,8 @@ func (b *configMapVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
return nil
|
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 {
|
if defaultMode == nil {
|
||||||
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
||||||
}
|
}
|
||||||
|
|
|
@ -238,7 +238,7 @@ func TestMakePayload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
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 {
|
if err != nil && tc.success {
|
||||||
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -175,7 +175,7 @@ func (b *downwardAPIVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
return err
|
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 {
|
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())
|
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
|
return err
|
||||||
|
@ -203,17 +203,19 @@ func (b *downwardAPIVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
return nil
|
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 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.
|
// 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 {
|
if defaultMode == nil {
|
||||||
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
||||||
}
|
}
|
||||||
|
|
||||||
errlist := []error{}
|
errlist := []error{}
|
||||||
data := make(map[string]volumeutil.FileProjection)
|
data := make(map[string]volumeutil.FileProjection)
|
||||||
for _, fileInfo := range d.items {
|
for _, fileInfo := range items {
|
||||||
var fileProjection volumeutil.FileProjection
|
var fileProjection volumeutil.FileProjection
|
||||||
fPath := path.Clean(fileInfo.Path)
|
fPath := path.Clean(fileInfo.Path)
|
||||||
if fileInfo.Mode != nil {
|
if fileInfo.Mode != nil {
|
||||||
|
@ -223,7 +225,7 @@ func (d *downwardAPIVolume) collectData(defaultMode *int32) (map[string]volumeut
|
||||||
}
|
}
|
||||||
if fileInfo.FieldRef != nil {
|
if fileInfo.FieldRef != nil {
|
||||||
// TODO: unify with Kubelet.podFieldSelectorRuntimeValue
|
// 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())
|
glog.Errorf("Unable to extract field %s: %s", fileInfo.FieldRef.FieldPath, err.Error())
|
||||||
errlist = append(errlist, err)
|
errlist = append(errlist, err)
|
||||||
} else {
|
} else {
|
||||||
|
@ -231,10 +233,10 @@ func (d *downwardAPIVolume) collectData(defaultMode *int32) (map[string]volumeut
|
||||||
}
|
}
|
||||||
} else if fileInfo.ResourceFieldRef != nil {
|
} else if fileInfo.ResourceFieldRef != nil {
|
||||||
containerName := fileInfo.ResourceFieldRef.ContainerName
|
containerName := fileInfo.ResourceFieldRef.ContainerName
|
||||||
nodeAllocatable, err := d.plugin.host.GetNodeAllocatable()
|
nodeAllocatable, err := host.GetNodeAllocatable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errlist = append(errlist, err)
|
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())
|
glog.Errorf("Unable to extract field %s: %s", fileInfo.ResourceFieldRef.Resource, err.Error())
|
||||||
errlist = append(errlist, err)
|
errlist = append(errlist, err)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -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
|
@ -208,7 +208,7 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
len(secret.Data),
|
len(secret.Data),
|
||||||
totalBytes)
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -235,7 +235,8 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error {
|
||||||
return nil
|
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 {
|
if defaultMode == nil {
|
||||||
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
return nil, fmt.Errorf("No defaultMode used, not even the default value for it")
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,7 +241,7 @@ func TestMakePayload(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
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 {
|
if err != nil && tc.success {
|
||||||
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
t.Errorf("%v: unexpected failure making payload: %v", tc.name, err)
|
||||||
continue
|
continue
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue