mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-10-31 02:08:13 +00:00 
			
		
		
		
	Allow Optional ConfigMap and Secrets
- ConfigMaps and Secrets for Env or Volumes are allowed to be optional
This commit is contained in:
		| @@ -286,6 +286,10 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz | ||||
| 		func(s *api.SecretVolumeSource, c fuzz.Continue) { | ||||
| 			c.FuzzNoCustom(s) // fuzz self without calling this function again | ||||
|  | ||||
| 			if c.RandBool() { | ||||
| 				opt := c.RandBool() | ||||
| 				s.Optional = &opt | ||||
| 			} | ||||
| 			// DefaultMode should always be set, it has a default | ||||
| 			// value and it is expected to be between 0 and 0777 | ||||
| 			var mode int32 | ||||
| @@ -296,6 +300,10 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz | ||||
| 		func(cm *api.ConfigMapVolumeSource, c fuzz.Continue) { | ||||
| 			c.FuzzNoCustom(cm) // fuzz self without calling this function again | ||||
|  | ||||
| 			if c.RandBool() { | ||||
| 				opt := c.RandBool() | ||||
| 				cm.Optional = &opt | ||||
| 			} | ||||
| 			// DefaultMode should always be set, it has a default | ||||
| 			// value and it is expected to be between 0 and 0777 | ||||
| 			var mode int32 | ||||
| @@ -401,6 +409,10 @@ func FuzzerFor(t *testing.T, version schema.GroupVersion, src rand.Source) *fuzz | ||||
| 		}, | ||||
| 		func(cm *api.ConfigMapEnvSource, c fuzz.Continue) { | ||||
| 			c.FuzzNoCustom(cm) // fuzz self without calling this function again | ||||
| 			if c.RandBool() { | ||||
| 				opt := c.RandBool() | ||||
| 				cm.Optional = &opt | ||||
| 			} | ||||
| 		}, | ||||
| 		func(s *api.SecretEnvSource, c fuzz.Continue) { | ||||
| 			c.FuzzNoCustom(s) // fuzz self without calling this function again | ||||
|   | ||||
| @@ -729,8 +729,8 @@ type SecretVolumeSource struct { | ||||
| 	// key and content is the value. If specified, the listed keys will be | ||||
| 	// projected into the specified paths, and unlisted keys will not be | ||||
| 	// present. If a key is specified which is not present in the Secret, | ||||
| 	// the volume setup will error. Paths must be relative and may not contain | ||||
| 	// the '..' path or start with '..'. | ||||
| 	// 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 | ||||
| 	// Mode bits to use on created files by default. Must be a value between | ||||
| @@ -740,6 +740,9 @@ 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 | ||||
| 	// +optional | ||||
| 	Optional *bool | ||||
| } | ||||
|  | ||||
| // Represents an NFS mount that lasts the lifetime of a pod. | ||||
| @@ -992,8 +995,8 @@ type ConfigMapVolumeSource struct { | ||||
| 	// 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. Paths must be relative and may not contain | ||||
| 	// the '..' path or start with '..'. | ||||
| 	// 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 | ||||
| 	// Mode bits to use on created files by default. Must be a value between | ||||
| @@ -1003,6 +1006,9 @@ type ConfigMapVolumeSource struct { | ||||
| 	// mode, like fsGroup, and the result can be other mode bits set. | ||||
| 	// +optional | ||||
| 	DefaultMode *int32 | ||||
| 	// Specify whether the ConfigMap or it's keys must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool | ||||
| } | ||||
|  | ||||
| // Maps a string key to a path within a volume. | ||||
| @@ -1124,6 +1130,9 @@ type ConfigMapKeySelector struct { | ||||
| 	LocalObjectReference | ||||
| 	// The key to select. | ||||
| 	Key string | ||||
| 	// Specify whether the ConfigMap or it's key must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool | ||||
| } | ||||
|  | ||||
| // SecretKeySelector selects a key of a Secret. | ||||
| @@ -1132,6 +1141,9 @@ type SecretKeySelector struct { | ||||
| 	LocalObjectReference | ||||
| 	// The key of the secret to select from.  Must be a valid secret key. | ||||
| 	Key string | ||||
| 	// Specify whether the Secret or it's key must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool | ||||
| } | ||||
|  | ||||
| // EnvFromSource represents the source of a set of ConfigMaps | ||||
| @@ -1155,6 +1167,9 @@ type EnvFromSource struct { | ||||
| type ConfigMapEnvSource struct { | ||||
| 	// The ConfigMap to select from. | ||||
| 	LocalObjectReference | ||||
| 	// Specify whether the ConfigMap must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool | ||||
| } | ||||
|  | ||||
| // SecretEnvSource selects a Secret to populate the environment | ||||
| @@ -1165,6 +1180,9 @@ type ConfigMapEnvSource struct { | ||||
| type SecretEnvSource struct { | ||||
| 	// The Secret to select from. | ||||
| 	LocalObjectReference | ||||
| 	// Specify whether the Secret must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool | ||||
| } | ||||
|  | ||||
| // HTTPHeader describes a custom header to be used in HTTP probes | ||||
|   | ||||
| @@ -924,8 +924,8 @@ type SecretVolumeSource struct { | ||||
| 	// key and content is the value. If specified, the listed keys will be | ||||
| 	// projected into the specified paths, and unlisted keys will not be | ||||
| 	// present. If a key is specified which is not present in the Secret, | ||||
| 	// the volume setup will error. Paths must be relative and may not contain | ||||
| 	// the '..' path or start with '..'. | ||||
| 	// 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"` | ||||
| 	// Optional: mode bits to use on created files by default. Must be a | ||||
| @@ -935,6 +935,9 @@ type SecretVolumeSource struct { | ||||
| 	// mode, like fsGroup, and the result can be other mode bits set. | ||||
| 	// +optional | ||||
| 	DefaultMode *int32 `json:"defaultMode,omitempty" protobuf:"bytes,3,opt,name=defaultMode"` | ||||
| 	// Specify whether the Secret or it's keys must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool `json:"optional,omitempty" protobuf:"varint,4,opt,name=optional"` | ||||
| } | ||||
|  | ||||
| const ( | ||||
| @@ -1081,8 +1084,8 @@ type ConfigMapVolumeSource struct { | ||||
| 	// 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. Paths must be relative and may not contain | ||||
| 	// the '..' path or start with '..'. | ||||
| 	// 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"` | ||||
| 	// Optional: mode bits to use on created files by default. Must be a | ||||
| @@ -1092,6 +1095,9 @@ type ConfigMapVolumeSource struct { | ||||
| 	// mode, like fsGroup, and the result can be other mode bits set. | ||||
| 	// +optional | ||||
| 	DefaultMode *int32 `json:"defaultMode,omitempty" protobuf:"varint,3,opt,name=defaultMode"` | ||||
| 	// Specify whether the ConfigMap or it's keys must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool `json:"optional,omitempty" protobuf:"varint,4,opt,name=optional"` | ||||
| } | ||||
|  | ||||
| const ( | ||||
| @@ -1225,6 +1231,9 @@ type ConfigMapKeySelector struct { | ||||
| 	LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` | ||||
| 	// The key to select. | ||||
| 	Key string `json:"key" protobuf:"bytes,2,opt,name=key"` | ||||
| 	// Specify whether the ConfigMap or it's key must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool `json:"optional,omitempty" protobuf:"varint,3,opt,name=optional"` | ||||
| } | ||||
|  | ||||
| // SecretKeySelector selects a key of a Secret. | ||||
| @@ -1233,6 +1242,9 @@ type SecretKeySelector struct { | ||||
| 	LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` | ||||
| 	// The key of the secret to select from.  Must be a valid secret key. | ||||
| 	Key string `json:"key" protobuf:"bytes,2,opt,name=key"` | ||||
| 	// Specify whether the Secret or it's key must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool `json:"optional,omitempty" protobuf:"varint,3,opt,name=optional"` | ||||
| } | ||||
|  | ||||
| // EnvFromSource represents the source of a set of ConfigMaps | ||||
| @@ -1256,6 +1268,9 @@ type EnvFromSource struct { | ||||
| type ConfigMapEnvSource struct { | ||||
| 	// The ConfigMap to select from. | ||||
| 	LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` | ||||
| 	// Specify whether the ConfigMap must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool `json:"optional,omitempty" protobuf:"varint,2,opt,name=optional"` | ||||
| } | ||||
|  | ||||
| // SecretEnvSource selects a Secret to populate the environment | ||||
| @@ -1266,6 +1281,9 @@ type ConfigMapEnvSource struct { | ||||
| type SecretEnvSource struct { | ||||
| 	// The Secret to select from. | ||||
| 	LocalObjectReference `json:",inline" protobuf:"bytes,1,opt,name=localObjectReference"` | ||||
| 	// Specify whether the Secret must be defined | ||||
| 	// +optional | ||||
| 	Optional *bool `json:"optional,omitempty" protobuf:"varint,2,opt,name=optional"` | ||||
| } | ||||
|  | ||||
| // HTTPHeader describes a custom header to be used in HTTP probes | ||||
|   | ||||
| @@ -650,13 +650,19 @@ func printGitRepoVolumeSource(git *api.GitRepoVolumeSource, w *PrefixWriter) { | ||||
| } | ||||
|  | ||||
| func printSecretVolumeSource(secret *api.SecretVolumeSource, w *PrefixWriter) { | ||||
| 	optional := secret.Optional != nil && *secret.Optional | ||||
| 	w.Write(LEVEL_2, "Type:\tSecret (a volume populated by a Secret)\n"+ | ||||
| 		"    SecretName:\t%v\n", secret.SecretName) | ||||
| 		"    SecretName:\t%v\n", | ||||
| 		"    Optional:\t%v\n", | ||||
| 		secret.SecretName, optional) | ||||
| } | ||||
|  | ||||
| func printConfigMapVolumeSource(configMap *api.ConfigMapVolumeSource, w *PrefixWriter) { | ||||
| 	optional := configMap.Optional != nil && *configMap.Optional | ||||
| 	w.Write(LEVEL_2, "Type:\tConfigMap (a volume populated by a ConfigMap)\n"+ | ||||
| 		"    Name:\t%v\n", configMap.Name) | ||||
| 		"    Name:\t%v\n"+ | ||||
| 		"    Optional:\t%v\n", | ||||
| 		configMap.Name, optional) | ||||
| } | ||||
|  | ||||
| func printNFSVolumeSource(nfs *api.NFSVolumeSource, w *PrefixWriter) { | ||||
| @@ -1037,9 +1043,11 @@ func describeContainerEnvVars(container api.Container, resolverFn EnvVarResolver | ||||
| 			} | ||||
| 			w.Write(LEVEL_3, "%s:\t%s (%s)\n", e.Name, valueFrom, resource) | ||||
| 		case e.ValueFrom.SecretKeyRef != nil: | ||||
| 			w.Write(LEVEL_3, "%s:\t<set to the key '%s' in secret '%s'>\n", e.Name, e.ValueFrom.SecretKeyRef.Key, e.ValueFrom.SecretKeyRef.Name) | ||||
| 			optional := e.ValueFrom.SecretKeyRef.Optional != nil && *e.ValueFrom.SecretKeyRef.Optional | ||||
| 			w.Write(LEVEL_3, "%s:\t<set to the key '%s' in secret '%s'>\tOptional: %t\n", e.Name, e.ValueFrom.SecretKeyRef.Key, e.ValueFrom.SecretKeyRef.Name, optional) | ||||
| 		case e.ValueFrom.ConfigMapKeyRef != nil: | ||||
| 			w.Write(LEVEL_3, "%s:\t<set to the key '%s' of config map '%s'>\n", e.Name, e.ValueFrom.ConfigMapKeyRef.Key, e.ValueFrom.ConfigMapKeyRef.Name) | ||||
| 			optional := e.ValueFrom.ConfigMapKeyRef.Optional != nil && *e.ValueFrom.ConfigMapKeyRef.Optional | ||||
| 			w.Write(LEVEL_3, "%s:\t<set to the key '%s' of config map '%s'>\tOptional: %t\n", e.Name, e.ValueFrom.ConfigMapKeyRef.Key, e.ValueFrom.ConfigMapKeyRef.Name, optional) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| @@ -1054,17 +1062,20 @@ func describeContainerEnvFrom(container api.Container, resolverFn EnvVarResolver | ||||
| 	for _, e := range container.EnvFrom { | ||||
| 		from := "" | ||||
| 		name := "" | ||||
| 		optional := false | ||||
| 		if e.ConfigMapRef != nil { | ||||
| 			from = "ConfigMap" | ||||
| 			name = e.ConfigMapRef.Name | ||||
| 			optional = e.ConfigMapRef.Optional != nil && *e.ConfigMapRef.Optional | ||||
| 		} else if e.SecretRef != nil { | ||||
| 			from = "Secret" | ||||
| 			name = e.SecretRef.Name | ||||
| 			optional = e.SecretRef.Optional != nil && *e.SecretRef.Optional | ||||
| 		} | ||||
| 		if len(e.Prefix) == 0 { | ||||
| 			w.Write(LEVEL_3, "%s\t%s\n", name, from) | ||||
| 			w.Write(LEVEL_3, "%s\t%s\tOptional: %t\n", name, from, optional) | ||||
| 		} else { | ||||
| 			w.Write(LEVEL_3, "%s\t%s with prefix '%s'\n", name, from, e.Prefix) | ||||
| 			w.Write(LEVEL_3, "%s\t%s with prefix '%s'\tOptional: %t\n", name, from, e.Prefix, optional) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|   | ||||
| @@ -201,6 +201,7 @@ func VerifyDatesInOrder( | ||||
| } | ||||
|  | ||||
| func TestDescribeContainers(t *testing.T) { | ||||
| 	trueVal := true | ||||
| 	testCases := []struct { | ||||
| 		container        api.Container | ||||
| 		status           api.ContainerStatus | ||||
| @@ -295,7 +296,7 @@ func TestDescribeContainers(t *testing.T) { | ||||
| 				Ready:        true, | ||||
| 				RestartCount: 7, | ||||
| 			}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap"}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: false"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", ConfigMapRef: &api.ConfigMapEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, | ||||
| @@ -304,16 +305,25 @@ func TestDescribeContainers(t *testing.T) { | ||||
| 				Ready:        true, | ||||
| 				RestartCount: 7, | ||||
| 			}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap with prefix 'p_'"}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap with prefix 'p_'\tOptional: false"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, | ||||
| 			container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{ConfigMapRef: &api.ConfigMapEnvSource{Optional: &trueVal, LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, | ||||
| 			status: api.ContainerStatus{ | ||||
| 				Name:         "test", | ||||
| 				Ready:        true, | ||||
| 				RestartCount: 7, | ||||
| 			}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret"}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tConfigMap\tOptional: true"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}, Optional: &trueVal}}}}, | ||||
| 			status: api.ContainerStatus{ | ||||
| 				Name:         "test", | ||||
| 				Ready:        true, | ||||
| 				RestartCount: 7, | ||||
| 			}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret\tOptional: true"}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			container: api.Container{Name: "test", Image: "image", Env: []api.EnvVar{{Name: "envname", Value: "xyz"}}, EnvFrom: []api.EnvFromSource{{Prefix: "p_", SecretRef: &api.SecretEnvSource{LocalObjectReference: api.LocalObjectReference{Name: "a123"}}}}}, | ||||
| @@ -322,7 +332,7 @@ func TestDescribeContainers(t *testing.T) { | ||||
| 				Ready:        true, | ||||
| 				RestartCount: 7, | ||||
| 			}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret with prefix 'p_'"}, | ||||
| 			expectedElements: []string{"test", "State", "Waiting", "Ready", "True", "Restart Count", "7", "Image", "image", "envname", "xyz", "a123\tSecret with prefix 'p_'\tOptional: false"}, | ||||
| 		}, | ||||
| 		// Command | ||||
| 		{ | ||||
|   | ||||
| @@ -33,6 +33,7 @@ import ( | ||||
| 	"time" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| @@ -427,14 +428,20 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container | ||||
| 	for _, envFrom := range container.EnvFrom { | ||||
| 		switch { | ||||
| 		case envFrom.ConfigMapRef != nil: | ||||
| 			name := envFrom.ConfigMapRef.Name | ||||
| 			cm := envFrom.ConfigMapRef | ||||
| 			name := cm.Name | ||||
| 			configMap, ok := configMaps[name] | ||||
| 			if !ok { | ||||
| 				if kl.kubeClient == nil { | ||||
| 					return result, fmt.Errorf("Couldn't get configMap %v/%v, no kubeClient defined", pod.Namespace, name) | ||||
| 				} | ||||
| 				optional := cm.Optional != nil && *cm.Optional | ||||
| 				configMap, err = kl.kubeClient.Core().ConfigMaps(pod.Namespace).Get(name, metav1.GetOptions{}) | ||||
| 				if err != nil { | ||||
| 					if errors.IsNotFound(err) && optional { | ||||
| 						// ignore error when marked optional | ||||
| 						continue | ||||
| 					} | ||||
| 					return result, err | ||||
| 				} | ||||
| 				configMaps[name] = configMap | ||||
| @@ -450,14 +457,20 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container | ||||
| 				tmpEnv[k] = v | ||||
| 			} | ||||
| 		case envFrom.SecretRef != nil: | ||||
| 			name := envFrom.SecretRef.Name | ||||
| 			s := envFrom.SecretRef | ||||
| 			name := s.Name | ||||
| 			secret, ok := secrets[name] | ||||
| 			if !ok { | ||||
| 				if kl.kubeClient == nil { | ||||
| 					return result, fmt.Errorf("Couldn't get secret %v/%v, no kubeClient defined", pod.Namespace, name) | ||||
| 				} | ||||
| 				optional := s.Optional != nil && *s.Optional | ||||
| 				secret, err = kl.kubeClient.Core().Secrets(pod.Namespace).Get(name, metav1.GetOptions{}) | ||||
| 				if err != nil { | ||||
| 					if errors.IsNotFound(err) && optional { | ||||
| 						// ignore error when marked optional | ||||
| 						continue | ||||
| 					} | ||||
| 					return result, err | ||||
| 				} | ||||
| 				secrets[name] = secret | ||||
| @@ -510,8 +523,10 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container | ||||
| 					return result, err | ||||
| 				} | ||||
| 			case envVar.ValueFrom.ConfigMapKeyRef != nil: | ||||
| 				name := envVar.ValueFrom.ConfigMapKeyRef.Name | ||||
| 				key := envVar.ValueFrom.ConfigMapKeyRef.Key | ||||
| 				cm := envVar.ValueFrom.ConfigMapKeyRef | ||||
| 				name := cm.Name | ||||
| 				key := cm.Key | ||||
| 				optional := cm.Optional != nil && *cm.Optional | ||||
| 				configMap, ok := configMaps[name] | ||||
| 				if !ok { | ||||
| 					if kl.kubeClient == nil { | ||||
| @@ -519,17 +534,26 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container | ||||
| 					} | ||||
| 					configMap, err = kl.kubeClient.Core().ConfigMaps(pod.Namespace).Get(name, metav1.GetOptions{}) | ||||
| 					if err != nil { | ||||
| 						if errors.IsNotFound(err) && optional { | ||||
| 							// ignore error when marked optional | ||||
| 							continue | ||||
| 						} | ||||
| 						return result, err | ||||
| 					} | ||||
| 					configMaps[name] = configMap | ||||
| 				} | ||||
| 				runtimeVal, ok = configMap.Data[key] | ||||
| 				if !ok { | ||||
| 					if optional { | ||||
| 						continue | ||||
| 					} | ||||
| 					return result, fmt.Errorf("Couldn't find key %v in ConfigMap %v/%v", key, pod.Namespace, name) | ||||
| 				} | ||||
| 			case envVar.ValueFrom.SecretKeyRef != nil: | ||||
| 				name := envVar.ValueFrom.SecretKeyRef.Name | ||||
| 				key := envVar.ValueFrom.SecretKeyRef.Key | ||||
| 				s := envVar.ValueFrom.SecretKeyRef | ||||
| 				name := s.Name | ||||
| 				key := s.Key | ||||
| 				optional := s.Optional != nil && *s.Optional | ||||
| 				secret, ok := secrets[name] | ||||
| 				if !ok { | ||||
| 					if kl.kubeClient == nil { | ||||
| @@ -537,17 +561,30 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *v1.Pod, container *v1.Container | ||||
| 					} | ||||
| 					secret, err = kl.secretManager.GetSecret(pod.Namespace, name) | ||||
| 					if err != nil { | ||||
| 						if errors.IsNotFound(err) && optional { | ||||
| 							// ignore error when marked optional | ||||
| 							continue | ||||
| 						} | ||||
| 						return result, err | ||||
| 					} | ||||
| 					secrets[name] = secret | ||||
| 				} | ||||
| 				runtimeValBytes, ok := secret.Data[key] | ||||
| 				if !ok { | ||||
| 					if optional { | ||||
| 						continue | ||||
| 					} | ||||
| 					return result, fmt.Errorf("Couldn't find key %v in Secret %v/%v", key, pod.Namespace, name) | ||||
| 				} | ||||
| 				runtimeVal = string(runtimeValBytes) | ||||
| 			} | ||||
| 		} | ||||
| 		// Accesses apiserver+Pods. | ||||
| 		// So, the master may set service env vars, or kubelet may.  In case both are doing | ||||
| 		// it, we delete the key from the kubelet-generated ones so we don't have duplicate | ||||
| 		// env vars. | ||||
| 		// TODO: remove this next line once all platforms use apiserver+Pods. | ||||
| 		delete(serviceEnv, envVar.Name) | ||||
|  | ||||
| 		tmpEnv[envVar.Name] = runtimeVal | ||||
| 	} | ||||
|   | ||||
| @@ -26,6 +26,7 @@ import ( | ||||
|  | ||||
| 	"github.com/stretchr/testify/assert" | ||||
| 	"github.com/stretchr/testify/require" | ||||
| 	apierrors "k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/labels" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| @@ -257,6 +258,7 @@ func buildService(name, namespace, clusterIP, protocol string, port int) *v1.Ser | ||||
| } | ||||
|  | ||||
| func TestMakeEnvironmentVariables(t *testing.T) { | ||||
| 	trueVal := true | ||||
| 	services := []*v1.Service{ | ||||
| 		buildService("kubernetes", v1.NamespaceDefault, "1.2.3.1", "TCP", 8081), | ||||
| 		buildService("test", "test1", "1.2.3.3", "TCP", 8083), | ||||
| @@ -616,6 +618,106 @@ func TestMakeEnvironmentVariables(t *testing.T) { | ||||
| 				}, | ||||
| 			}, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "configmapkeyref_missing_optional", | ||||
| 			ns:   "test", | ||||
| 			container: &v1.Container{ | ||||
| 				Env: []v1.EnvVar{ | ||||
| 					{ | ||||
| 						Name: "POD_NAME", | ||||
| 						ValueFrom: &v1.EnvVarSource{ | ||||
| 							ConfigMapKeyRef: &v1.ConfigMapKeySelector{ | ||||
| 								LocalObjectReference: v1.LocalObjectReference{Name: "missing-config-map"}, | ||||
| 								Key:                  "key", | ||||
| 								Optional:             &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			masterServiceNs: "nothing", | ||||
| 			expectedEnvs:    nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "configmapkeyref_missing_key_optional", | ||||
| 			ns:   "test", | ||||
| 			container: &v1.Container{ | ||||
| 				Env: []v1.EnvVar{ | ||||
| 					{ | ||||
| 						Name: "POD_NAME", | ||||
| 						ValueFrom: &v1.EnvVarSource{ | ||||
| 							ConfigMapKeyRef: &v1.ConfigMapKeySelector{ | ||||
| 								LocalObjectReference: v1.LocalObjectReference{Name: "test-config-map"}, | ||||
| 								Key:                  "key", | ||||
| 								Optional:             &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			masterServiceNs: "nothing", | ||||
| 			nilLister:       true, | ||||
| 			configMap: &v1.ConfigMap{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{ | ||||
| 					Namespace: "test1", | ||||
| 					Name:      "test-configmap", | ||||
| 				}, | ||||
| 				Data: map[string]string{ | ||||
| 					"a": "b", | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedEnvs: nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "secretkeyref_missing_optional", | ||||
| 			ns:   "test", | ||||
| 			container: &v1.Container{ | ||||
| 				Env: []v1.EnvVar{ | ||||
| 					{ | ||||
| 						Name: "POD_NAME", | ||||
| 						ValueFrom: &v1.EnvVarSource{ | ||||
| 							SecretKeyRef: &v1.SecretKeySelector{ | ||||
| 								LocalObjectReference: v1.LocalObjectReference{Name: "missing-secret"}, | ||||
| 								Key:                  "key", | ||||
| 								Optional:             &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			masterServiceNs: "nothing", | ||||
| 			expectedEnvs:    nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "secretkeyref_missing_key_optional", | ||||
| 			ns:   "test", | ||||
| 			container: &v1.Container{ | ||||
| 				Env: []v1.EnvVar{ | ||||
| 					{ | ||||
| 						Name: "POD_NAME", | ||||
| 						ValueFrom: &v1.EnvVarSource{ | ||||
| 							SecretKeyRef: &v1.SecretKeySelector{ | ||||
| 								LocalObjectReference: v1.LocalObjectReference{Name: "test-secret"}, | ||||
| 								Key:                  "key", | ||||
| 								Optional:             &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			masterServiceNs: "nothing", | ||||
| 			nilLister:       true, | ||||
| 			secret: &v1.Secret{ | ||||
| 				ObjectMeta: metav1.ObjectMeta{ | ||||
| 					Namespace: "test1", | ||||
| 					Name:      "test-secret", | ||||
| 				}, | ||||
| 				Data: map[string][]byte{ | ||||
| 					"a": []byte("b"), | ||||
| 				}, | ||||
| 			}, | ||||
| 			expectedEnvs: nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "configmap", | ||||
| 			ns:   "test1", | ||||
| @@ -722,6 +824,19 @@ func TestMakeEnvironmentVariables(t *testing.T) { | ||||
| 			masterServiceNs: "nothing", | ||||
| 			expectedError:   true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "configmap_missing_optional", | ||||
| 			ns:   "test", | ||||
| 			container: &v1.Container{ | ||||
| 				EnvFrom: []v1.EnvFromSource{ | ||||
| 					{ConfigMapRef: &v1.ConfigMapEnvSource{ | ||||
| 						Optional:             &trueVal, | ||||
| 						LocalObjectReference: v1.LocalObjectReference{Name: "missing-config-map"}}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			masterServiceNs: "nothing", | ||||
| 			expectedEnvs:    nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "configmap_invalid_keys", | ||||
| 			ns:   "test1", | ||||
| @@ -876,6 +991,19 @@ func TestMakeEnvironmentVariables(t *testing.T) { | ||||
| 			masterServiceNs: "nothing", | ||||
| 			expectedError:   true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "secret_missing_optional", | ||||
| 			ns:   "test", | ||||
| 			container: &v1.Container{ | ||||
| 				EnvFrom: []v1.EnvFromSource{ | ||||
| 					{SecretRef: &v1.SecretEnvSource{ | ||||
| 						LocalObjectReference: v1.LocalObjectReference{Name: "missing-secret"}, | ||||
| 						Optional:             &trueVal}}, | ||||
| 				}, | ||||
| 			}, | ||||
| 			masterServiceNs: "nothing", | ||||
| 			expectedEnvs:    nil, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "secret_invalid_keys", | ||||
| 			ns:   "test1", | ||||
| @@ -940,10 +1068,17 @@ func TestMakeEnvironmentVariables(t *testing.T) { | ||||
| 		testKubelet.fakeKubeClient.AddReactor("get", "configmaps", func(action core.Action) (bool, runtime.Object, error) { | ||||
| 			var err error | ||||
| 			if tc.configMap == nil { | ||||
| 				err = errors.New("no configmap defined") | ||||
| 				err = apierrors.NewNotFound(action.GetResource().GroupResource(), "configmap-name") | ||||
| 			} | ||||
| 			return true, tc.configMap, err | ||||
| 		}) | ||||
| 		testKubelet.fakeKubeClient.AddReactor("get", "secrets", func(action core.Action) (bool, runtime.Object, error) { | ||||
| 			var err error | ||||
| 			if tc.secret == nil { | ||||
| 				err = apierrors.NewNotFound(action.GetResource().GroupResource(), "secret-name") | ||||
| 			} | ||||
| 			return true, tc.secret, err | ||||
| 		}) | ||||
|  | ||||
| 		testKubelet.fakeKubeClient.AddReactor("get", "secrets", func(action core.Action) (bool, runtime.Object, error) { | ||||
| 			var err error | ||||
|   | ||||
| @@ -173,8 +173,12 @@ func newTestKubeletWithImageList( | ||||
| 	kubelet.cadvisor = mockCadvisor | ||||
|  | ||||
| 	fakeMirrorClient := podtest.NewFakeMirrorClient() | ||||
| 	fakeSecretManager := secret.NewFakeManager() | ||||
| 	kubelet.podManager = kubepod.NewBasicPodManager(fakeMirrorClient, fakeSecretManager) | ||||
| 	secretManager, err := secret.NewSimpleSecretManager(kubelet.kubeClient) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("can't create a secret manager: %v", err) | ||||
| 	} | ||||
| 	kubelet.secretManager = secretManager | ||||
| 	kubelet.podManager = kubepod.NewBasicPodManager(fakeMirrorClient, kubelet.secretManager) | ||||
| 	kubelet.statusManager = status.NewManager(fakeKubeClient, kubelet.podManager) | ||||
| 	kubelet.containerRefManager = kubecontainer.NewRefManager() | ||||
| 	diskSpaceManager, err := newDiskSpaceManager(mockCadvisor, DiskSpacePolicy{}) | ||||
| @@ -249,7 +253,7 @@ func newTestKubeletWithImageList( | ||||
|  | ||||
| 	plug := &volumetest.FakeVolumePlugin{PluginName: "fake", Host: nil} | ||||
| 	kubelet.volumePluginMgr, err = | ||||
| 		NewInitializedVolumePluginMgr(kubelet, fakeSecretManager, []volume.VolumePlugin{plug}) | ||||
| 		NewInitializedVolumePluginMgr(kubelet, kubelet.secretManager, []volume.VolumePlugin{plug}) | ||||
| 	require.NoError(t, err, "Failed to initialize VolumePluginMgr") | ||||
|  | ||||
| 	kubelet.mounter = &mount.FakeMounter{} | ||||
|   | ||||
| @@ -20,6 +20,7 @@ import ( | ||||
| 	"fmt" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| @@ -170,10 +171,19 @@ func (b *configMapVolumeMounter) SetUpAt(dir string, fsGroup *int64) error { | ||||
| 		return fmt.Errorf("Cannot setup configMap volume %v because kube client is not configured", b.volName) | ||||
| 	} | ||||
|  | ||||
| 	optional := b.source.Optional != nil && *b.source.Optional | ||||
| 	configMap, err := kubeClient.Core().ConfigMaps(b.pod.Namespace).Get(b.source.Name, metav1.GetOptions{}) | ||||
| 	if err != nil { | ||||
| 		glog.Errorf("Couldn't get configMap %v/%v: %v", b.pod.Namespace, b.source.Name, err) | ||||
| 		return err | ||||
| 		if !(errors.IsNotFound(err) && optional) { | ||||
| 			glog.Errorf("Couldn't get configMap %v/%v: %v", b.pod.Namespace, b.source.Name, err) | ||||
| 			return err | ||||
| 		} | ||||
| 		configMap = &v1.ConfigMap{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: b.pod.Namespace, | ||||
| 				Name:      b.source.Name, | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	totalBytes := totalBytes(configMap) | ||||
| @@ -183,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) | ||||
| 	payload, err := makePayload(b.source.Items, configMap, b.source.DefaultMode, optional) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -210,7 +220,7 @@ func (b *configMapVolumeMounter) SetUpAt(dir string, fsGroup *int64) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func makePayload(mappings []v1.KeyToPath, configMap *v1.ConfigMap, defaultMode *int32) (map[string]volumeutil.FileProjection, error) { | ||||
| 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") | ||||
| 	} | ||||
| @@ -228,6 +238,9 @@ func makePayload(mappings []v1.KeyToPath, configMap *v1.ConfigMap, defaultMode * | ||||
| 		for _, ktp := range mappings { | ||||
| 			content, ok := configMap.Data[ktp.Key] | ||||
| 			if !ok { | ||||
| 				if optional { | ||||
| 					continue | ||||
| 				} | ||||
| 				err_msg := "references non-existent config key" | ||||
| 				glog.Errorf(err_msg) | ||||
| 				return nil, fmt.Errorf(err_msg) | ||||
|   | ||||
| @@ -43,6 +43,7 @@ func TestMakePayload(t *testing.T) { | ||||
| 		mappings  []v1.KeyToPath | ||||
| 		configMap *v1.ConfigMap | ||||
| 		mode      int32 | ||||
| 		optional  bool | ||||
| 		payload   map[string]util.FileProjection | ||||
| 		success   bool | ||||
| 	}{ | ||||
| @@ -215,10 +216,29 @@ func TestMakePayload(t *testing.T) { | ||||
| 			}, | ||||
| 			success: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "optional non existent key", | ||||
| 			mappings: []v1.KeyToPath{ | ||||
| 				{ | ||||
| 					Key:  "zab", | ||||
| 					Path: "path/to/foo.txt", | ||||
| 				}, | ||||
| 			}, | ||||
| 			configMap: &v1.ConfigMap{ | ||||
| 				Data: map[string]string{ | ||||
| 					"foo": "foo", | ||||
| 					"bar": "bar", | ||||
| 				}, | ||||
| 			}, | ||||
| 			mode:     0644, | ||||
| 			optional: true, | ||||
| 			payload:  map[string]util.FileProjection{}, | ||||
| 			success:  true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range cases { | ||||
| 		actualPayload, err := makePayload(tc.mappings, tc.configMap, &tc.mode) | ||||
| 		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 | ||||
| @@ -388,6 +408,143 @@ func TestPluginReboot(t *testing.T) { | ||||
| 	doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) | ||||
| } | ||||
|  | ||||
| func TestPluginOptional(t *testing.T) { | ||||
| 	var ( | ||||
| 		testPodUID     = types.UID("test_pod_uid") | ||||
| 		testVolumeName = "test_volume_name" | ||||
| 		testNamespace  = "test_configmap_namespace" | ||||
| 		testName       = "test_configmap_name" | ||||
| 		trueVal        = true | ||||
|  | ||||
| 		volumeSpec    = volumeSpec(testVolumeName, testName, 0644) | ||||
| 		client        = fake.NewSimpleClientset() | ||||
| 		pluginMgr     = volume.VolumePluginMgr{} | ||||
| 		tempDir, host = newTestHost(t, client) | ||||
| 	) | ||||
| 	volumeSpec.VolumeSource.ConfigMap.Optional = &trueVal | ||||
|  | ||||
| 	defer os.RemoveAll(tempDir) | ||||
| 	pluginMgr.InitPlugins(ProbeVolumePlugins(), host) | ||||
|  | ||||
| 	plugin, err := pluginMgr.FindPluginByName(configMapPluginName) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Can't find the plugin by name") | ||||
| 	} | ||||
|  | ||||
| 	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} | ||||
| 	mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to make a new Mounter: %v", err) | ||||
| 	} | ||||
| 	if mounter == nil { | ||||
| 		t.Errorf("Got a nil Mounter") | ||||
| 	} | ||||
|  | ||||
| 	vName, err := plugin.GetVolumeName(volume.NewSpecFromVolume(volumeSpec)) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to GetVolumeName: %v", err) | ||||
| 	} | ||||
| 	if vName != "test_volume_name/test_configmap_name" { | ||||
| 		t.Errorf("Got unexpect VolumeName %v", vName) | ||||
| 	} | ||||
|  | ||||
| 	volumePath := mounter.GetPath() | ||||
| 	if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid/volumes/kubernetes.io~configmap/test_volume_name")) { | ||||
| 		t.Errorf("Got unexpected path: %s", volumePath) | ||||
| 	} | ||||
|  | ||||
| 	fsGroup := int64(1001) | ||||
| 	err = mounter.SetUp(&fsGroup) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to setup volume: %v", err) | ||||
| 	} | ||||
| 	if _, err := os.Stat(volumePath); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			t.Errorf("SetUp() failed, volume path not created: %s", volumePath) | ||||
| 		} else { | ||||
| 			t.Errorf("SetUp() failed: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	infos, err := ioutil.ReadDir(volumePath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("couldn't find volume path, %s", volumePath) | ||||
| 	} | ||||
| 	if len(infos) != 0 { | ||||
| 		t.Errorf("empty directory, %s, not found", volumePath) | ||||
| 	} | ||||
| 	doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) | ||||
| } | ||||
|  | ||||
| func TestPluginKeysOptional(t *testing.T) { | ||||
| 	var ( | ||||
| 		testPodUID     = types.UID("test_pod_uid") | ||||
| 		testVolumeName = "test_volume_name" | ||||
| 		testNamespace  = "test_configmap_namespace" | ||||
| 		testName       = "test_configmap_name" | ||||
| 		trueVal        = true | ||||
|  | ||||
| 		volumeSpec    = volumeSpec(testVolumeName, testName, 0644) | ||||
| 		configMap     = configMap(testNamespace, testName) | ||||
| 		client        = fake.NewSimpleClientset(&configMap) | ||||
| 		pluginMgr     = volume.VolumePluginMgr{} | ||||
| 		tempDir, host = newTestHost(t, client) | ||||
| 	) | ||||
| 	volumeSpec.VolumeSource.ConfigMap.Items = []v1.KeyToPath{ | ||||
| 		{Key: "data-1", Path: "data-1"}, | ||||
| 		{Key: "data-2", Path: "data-2"}, | ||||
| 		{Key: "data-3", Path: "data-3"}, | ||||
| 		{Key: "missing", Path: "missing"}, | ||||
| 	} | ||||
| 	volumeSpec.VolumeSource.ConfigMap.Optional = &trueVal | ||||
|  | ||||
| 	defer os.RemoveAll(tempDir) | ||||
| 	pluginMgr.InitPlugins(ProbeVolumePlugins(), host) | ||||
|  | ||||
| 	plugin, err := pluginMgr.FindPluginByName(configMapPluginName) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Can't find the plugin by name") | ||||
| 	} | ||||
|  | ||||
| 	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} | ||||
| 	mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to make a new Mounter: %v", err) | ||||
| 	} | ||||
| 	if mounter == nil { | ||||
| 		t.Errorf("Got a nil Mounter") | ||||
| 	} | ||||
|  | ||||
| 	vName, err := plugin.GetVolumeName(volume.NewSpecFromVolume(volumeSpec)) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to GetVolumeName: %v", err) | ||||
| 	} | ||||
| 	if vName != "test_volume_name/test_configmap_name" { | ||||
| 		t.Errorf("Got unexpect VolumeName %v", vName) | ||||
| 	} | ||||
|  | ||||
| 	volumePath := mounter.GetPath() | ||||
| 	if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid/volumes/kubernetes.io~configmap/test_volume_name")) { | ||||
| 		t.Errorf("Got unexpected path: %s", volumePath) | ||||
| 	} | ||||
|  | ||||
| 	fsGroup := int64(1001) | ||||
| 	err = mounter.SetUp(&fsGroup) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to setup volume: %v", err) | ||||
| 	} | ||||
| 	if _, err := os.Stat(volumePath); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			t.Errorf("SetUp() failed, volume path not created: %s", volumePath) | ||||
| 		} else { | ||||
| 			t.Errorf("SetUp() failed: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	doTestConfigMapDataInVolume(volumePath, configMap, t) | ||||
| 	doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) | ||||
| } | ||||
|  | ||||
| func volumeSpec(volumeName, configMapName string, defaultMode int32) *v1.Volume { | ||||
| 	return &v1.Volume{ | ||||
| 		Name: volumeName, | ||||
|   | ||||
| @@ -22,6 +22,8 @@ import ( | ||||
| 	"runtime" | ||||
|  | ||||
| 	"github.com/golang/glog" | ||||
| 	"k8s.io/apimachinery/pkg/api/errors" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/types" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| 	ioutil "k8s.io/kubernetes/pkg/util/io" | ||||
| @@ -191,10 +193,19 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	optional := b.source.Optional != nil && *b.source.Optional | ||||
| 	secret, err := b.getSecret(b.pod.Namespace, b.source.SecretName) | ||||
| 	if err != nil { | ||||
| 		glog.Errorf("Couldn't get secret %v/%v", b.pod.Namespace, b.source.SecretName) | ||||
| 		return err | ||||
| 		if !(errors.IsNotFound(err) && optional) { | ||||
| 			glog.Errorf("Couldn't get secret %v/%v", b.pod.Namespace, b.source.SecretName) | ||||
| 			return err | ||||
| 		} | ||||
| 		secret = &v1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: b.pod.Namespace, | ||||
| 				Name:      b.source.SecretName, | ||||
| 			}, | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	totalBytes := totalSecretBytes(secret) | ||||
| @@ -204,7 +215,7 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error { | ||||
| 		len(secret.Data), | ||||
| 		totalBytes) | ||||
|  | ||||
| 	payload, err := makePayload(b.source.Items, secret, b.source.DefaultMode) | ||||
| 	payload, err := makePayload(b.source.Items, secret, b.source.DefaultMode, optional) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| @@ -231,7 +242,7 @@ func (b *secretVolumeMounter) SetUpAt(dir string, fsGroup *int64) error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func makePayload(mappings []v1.KeyToPath, secret *v1.Secret, defaultMode *int32) (map[string]volumeutil.FileProjection, error) { | ||||
| 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") | ||||
| 	} | ||||
| @@ -249,6 +260,9 @@ func makePayload(mappings []v1.KeyToPath, secret *v1.Secret, defaultMode *int32) | ||||
| 		for _, ktp := range mappings { | ||||
| 			content, ok := secret.Data[ktp.Key] | ||||
| 			if !ok { | ||||
| 				if optional { | ||||
| 					continue | ||||
| 				} | ||||
| 				err_msg := "references non-existent secret key" | ||||
| 				glog.Errorf(err_msg) | ||||
| 				return nil, fmt.Errorf(err_msg) | ||||
|   | ||||
| @@ -46,6 +46,7 @@ func TestMakePayload(t *testing.T) { | ||||
| 		mappings []v1.KeyToPath | ||||
| 		secret   *v1.Secret | ||||
| 		mode     int32 | ||||
| 		optional bool | ||||
| 		payload  map[string]util.FileProjection | ||||
| 		success  bool | ||||
| 	}{ | ||||
| @@ -218,10 +219,29 @@ func TestMakePayload(t *testing.T) { | ||||
| 			}, | ||||
| 			success: true, | ||||
| 		}, | ||||
| 		{ | ||||
| 			name: "optional non existent key", | ||||
| 			mappings: []v1.KeyToPath{ | ||||
| 				{ | ||||
| 					Key:  "zab", | ||||
| 					Path: "path/to/foo.txt", | ||||
| 				}, | ||||
| 			}, | ||||
| 			secret: &v1.Secret{ | ||||
| 				Data: map[string][]byte{ | ||||
| 					"foo": []byte("foo"), | ||||
| 					"bar": []byte("bar"), | ||||
| 				}, | ||||
| 			}, | ||||
| 			mode:     0644, | ||||
| 			optional: true, | ||||
| 			payload:  map[string]util.FileProjection{}, | ||||
| 			success:  true, | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	for _, tc := range cases { | ||||
| 		actualPayload, err := makePayload(tc.mappings, tc.secret, &tc.mode) | ||||
| 		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 | ||||
| @@ -398,6 +418,154 @@ func TestPluginReboot(t *testing.T) { | ||||
| 	doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) | ||||
| } | ||||
|  | ||||
| func TestPluginOptional(t *testing.T) { | ||||
| 	var ( | ||||
| 		testPodUID     = types.UID("test_pod_uid") | ||||
| 		testVolumeName = "test_volume_name" | ||||
| 		testNamespace  = "test_secret_namespace" | ||||
| 		testName       = "test_secret_name" | ||||
| 		trueVal        = true | ||||
|  | ||||
| 		volumeSpec    = volumeSpec(testVolumeName, testName, 0644) | ||||
| 		client        = fake.NewSimpleClientset() | ||||
| 		pluginMgr     = volume.VolumePluginMgr{} | ||||
| 		rootDir, host = newTestHost(t, client) | ||||
| 	) | ||||
| 	volumeSpec.Secret.Optional = &trueVal | ||||
| 	defer os.RemoveAll(rootDir) | ||||
| 	pluginMgr.InitPlugins(ProbeVolumePlugins(), host) | ||||
|  | ||||
| 	plugin, err := pluginMgr.FindPluginByName(secretPluginName) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Can't find the plugin by name") | ||||
| 	} | ||||
|  | ||||
| 	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} | ||||
| 	mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to make a new Mounter: %v", err) | ||||
| 	} | ||||
| 	if mounter == nil { | ||||
| 		t.Errorf("Got a nil Mounter") | ||||
| 	} | ||||
|  | ||||
| 	volumePath := mounter.GetPath() | ||||
| 	if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid/volumes/kubernetes.io~secret/test_volume_name")) { | ||||
| 		t.Errorf("Got unexpected path: %s", volumePath) | ||||
| 	} | ||||
|  | ||||
| 	err = mounter.SetUp(nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to setup volume: %v", err) | ||||
| 	} | ||||
| 	if _, err := os.Stat(volumePath); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			t.Errorf("SetUp() failed, volume path not created: %s", volumePath) | ||||
| 		} else { | ||||
| 			t.Errorf("SetUp() failed: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// secret volume should create its own empty wrapper path | ||||
| 	podWrapperMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid/plugins/kubernetes.io~empty-dir/wrapped_test_volume_name", rootDir) | ||||
|  | ||||
| 	if _, err := os.Stat(podWrapperMetadataDir); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			t.Errorf("SetUp() failed, empty-dir wrapper path is not created: %s", podWrapperMetadataDir) | ||||
| 		} else { | ||||
| 			t.Errorf("SetUp() failed: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	infos, err := ioutil.ReadDir(volumePath) | ||||
| 	if err != nil { | ||||
| 		t.Fatalf("couldn't find volume path, %s", volumePath) | ||||
| 	} | ||||
| 	if len(infos) != 0 { | ||||
| 		t.Errorf("empty directory, %s, not found", volumePath) | ||||
| 	} | ||||
|  | ||||
| 	defer doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) | ||||
| } | ||||
|  | ||||
| func TestPluginOptionalKeys(t *testing.T) { | ||||
| 	var ( | ||||
| 		testPodUID     = types.UID("test_pod_uid") | ||||
| 		testVolumeName = "test_volume_name" | ||||
| 		testNamespace  = "test_secret_namespace" | ||||
| 		testName       = "test_secret_name" | ||||
| 		trueVal        = true | ||||
|  | ||||
| 		volumeSpec    = volumeSpec(testVolumeName, testName, 0644) | ||||
| 		secret        = secret(testNamespace, testName) | ||||
| 		client        = fake.NewSimpleClientset(&secret) | ||||
| 		pluginMgr     = volume.VolumePluginMgr{} | ||||
| 		rootDir, host = newTestHost(t, client) | ||||
| 	) | ||||
| 	volumeSpec.VolumeSource.Secret.Items = []v1.KeyToPath{ | ||||
| 		{Key: "data-1", Path: "data-1"}, | ||||
| 		{Key: "data-2", Path: "data-2"}, | ||||
| 		{Key: "data-3", Path: "data-3"}, | ||||
| 		{Key: "missing", Path: "missing"}, | ||||
| 	} | ||||
| 	volumeSpec.Secret.Optional = &trueVal | ||||
| 	defer os.RemoveAll(rootDir) | ||||
| 	pluginMgr.InitPlugins(ProbeVolumePlugins(), host) | ||||
|  | ||||
| 	plugin, err := pluginMgr.FindPluginByName(secretPluginName) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Can't find the plugin by name") | ||||
| 	} | ||||
|  | ||||
| 	pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Namespace: testNamespace, UID: testPodUID}} | ||||
| 	mounter, err := plugin.NewMounter(volume.NewSpecFromVolume(volumeSpec), pod, volume.VolumeOptions{}) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to make a new Mounter: %v", err) | ||||
| 	} | ||||
| 	if mounter == nil { | ||||
| 		t.Errorf("Got a nil Mounter") | ||||
| 	} | ||||
|  | ||||
| 	volumePath := mounter.GetPath() | ||||
| 	if !strings.HasSuffix(volumePath, fmt.Sprintf("pods/test_pod_uid/volumes/kubernetes.io~secret/test_volume_name")) { | ||||
| 		t.Errorf("Got unexpected path: %s", volumePath) | ||||
| 	} | ||||
|  | ||||
| 	err = mounter.SetUp(nil) | ||||
| 	if err != nil { | ||||
| 		t.Errorf("Failed to setup volume: %v", err) | ||||
| 	} | ||||
| 	if _, err := os.Stat(volumePath); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			t.Errorf("SetUp() failed, volume path not created: %s", volumePath) | ||||
| 		} else { | ||||
| 			t.Errorf("SetUp() failed: %v", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// secret volume should create its own empty wrapper path | ||||
| 	podWrapperMetadataDir := fmt.Sprintf("%v/pods/test_pod_uid/plugins/kubernetes.io~empty-dir/wrapped_test_volume_name", rootDir) | ||||
|  | ||||
| 	if _, err := os.Stat(podWrapperMetadataDir); err != nil { | ||||
| 		if os.IsNotExist(err) { | ||||
| 			t.Errorf("SetUp() failed, empty-dir wrapper path is not created: %s", podWrapperMetadataDir) | ||||
| 		} else { | ||||
| 			t.Errorf("SetUp() failed: %v", err) | ||||
| 		} | ||||
| 	} | ||||
| 	doTestSecretDataInVolume(volumePath, secret, t) | ||||
| 	defer doTestCleanAndTeardown(plugin, testPodUID, testVolumeName, volumePath, t) | ||||
|  | ||||
| 	// Metrics only supported on linux | ||||
| 	metrics, err := mounter.GetMetrics() | ||||
| 	if runtime.GOOS == "linux" { | ||||
| 		assert.NotEmpty(t, metrics) | ||||
| 		assert.NoError(t, err) | ||||
| 	} else { | ||||
| 		t.Skipf("Volume metrics not supported on %s", runtime.GOOS) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func volumeSpec(volumeName, secretName string, defaultMode int32) *v1.Volume { | ||||
| 	return &v1.Volume{ | ||||
| 		Name: volumeName, | ||||
|   | ||||
| @@ -19,6 +19,7 @@ package common | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"time" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| @@ -154,6 +155,189 @@ var _ = framework.KubeDescribe("ConfigMap", func() { | ||||
| 		Eventually(pollLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("value-2")) | ||||
| 	}) | ||||
|  | ||||
| 	It("optional updates should be reflected in volume [Conformance] [Volume]", func() { | ||||
|  | ||||
| 		// We may have to wait or a full sync period to elapse before the | ||||
| 		// Kubelet projects the update into the volume and the container picks | ||||
| 		// it up. This timeout is based on the default Kubelet sync period (1 | ||||
| 		// minute) plus additional time for fudge factor. | ||||
| 		const podLogTimeout = 300 * time.Second | ||||
| 		trueVal := true | ||||
|  | ||||
| 		volumeMountPath := "/etc/configmap-volumes" | ||||
|  | ||||
| 		deleteName := "cm-test-opt-del-" + string(uuid.NewUUID()) | ||||
| 		deleteContainerName := "delcm-volume-test" | ||||
| 		deleteVolumeName := "deletecm-volume" | ||||
| 		deleteConfigMap := &v1.ConfigMap{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: f.Namespace.Name, | ||||
| 				Name:      deleteName, | ||||
| 			}, | ||||
| 			Data: map[string]string{ | ||||
| 				"data-1": "value-1", | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		updateName := "cm-test-opt-upd-" + string(uuid.NewUUID()) | ||||
| 		updateContainerName := "updcm-volume-test" | ||||
| 		updateVolumeName := "updatecm-volume" | ||||
| 		updateConfigMap := &v1.ConfigMap{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: f.Namespace.Name, | ||||
| 				Name:      updateName, | ||||
| 			}, | ||||
| 			Data: map[string]string{ | ||||
| 				"data-1": "value-1", | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		createName := "cm-test-opt-create-" + string(uuid.NewUUID()) | ||||
| 		createContainerName := "createcm-volume-test" | ||||
| 		createVolumeName := "createcm-volume" | ||||
| 		createConfigMap := &v1.ConfigMap{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: f.Namespace.Name, | ||||
| 				Name:      createName, | ||||
| 			}, | ||||
| 			Data: map[string]string{ | ||||
| 				"data-1": "value-1", | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		By(fmt.Sprintf("Creating configMap with name %s", deleteConfigMap.Name)) | ||||
| 		var err error | ||||
| 		if deleteConfigMap, err = f.ClientSet.Core().ConfigMaps(f.Namespace.Name).Create(deleteConfigMap); err != nil { | ||||
| 			framework.Failf("unable to create test configMap %s: %v", deleteConfigMap.Name, err) | ||||
| 		} | ||||
|  | ||||
| 		By(fmt.Sprintf("Creating configMap with name %s", updateConfigMap.Name)) | ||||
| 		if updateConfigMap, err = f.ClientSet.Core().ConfigMaps(f.Namespace.Name).Create(updateConfigMap); err != nil { | ||||
| 			framework.Failf("unable to create test configMap %s: %v", updateConfigMap.Name, err) | ||||
| 		} | ||||
|  | ||||
| 		pod := &v1.Pod{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name: "pod-configmaps-" + string(uuid.NewUUID()), | ||||
| 			}, | ||||
| 			Spec: v1.PodSpec{ | ||||
| 				Volumes: []v1.Volume{ | ||||
| 					{ | ||||
| 						Name: deleteVolumeName, | ||||
| 						VolumeSource: v1.VolumeSource{ | ||||
| 							ConfigMap: &v1.ConfigMapVolumeSource{ | ||||
| 								LocalObjectReference: v1.LocalObjectReference{ | ||||
| 									Name: deleteName, | ||||
| 								}, | ||||
| 								Optional: &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: updateVolumeName, | ||||
| 						VolumeSource: v1.VolumeSource{ | ||||
| 							ConfigMap: &v1.ConfigMapVolumeSource{ | ||||
| 								LocalObjectReference: v1.LocalObjectReference{ | ||||
| 									Name: updateName, | ||||
| 								}, | ||||
| 								Optional: &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: createVolumeName, | ||||
| 						VolumeSource: v1.VolumeSource{ | ||||
| 							ConfigMap: &v1.ConfigMapVolumeSource{ | ||||
| 								LocalObjectReference: v1.LocalObjectReference{ | ||||
| 									Name: createName, | ||||
| 								}, | ||||
| 								Optional: &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				Containers: []v1.Container{ | ||||
| 					{ | ||||
| 						Name:    deleteContainerName, | ||||
| 						Image:   "gcr.io/google_containers/mounttest:0.7", | ||||
| 						Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/configmap-volumes/delete/data-1"}, | ||||
| 						VolumeMounts: []v1.VolumeMount{ | ||||
| 							{ | ||||
| 								Name:      deleteVolumeName, | ||||
| 								MountPath: path.Join(volumeMountPath, "delete"), | ||||
| 								ReadOnly:  true, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:    updateContainerName, | ||||
| 						Image:   "gcr.io/google_containers/mounttest:0.7", | ||||
| 						Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/configmap-volumes/update/data-3"}, | ||||
| 						VolumeMounts: []v1.VolumeMount{ | ||||
| 							{ | ||||
| 								Name:      updateVolumeName, | ||||
| 								MountPath: path.Join(volumeMountPath, "update"), | ||||
| 								ReadOnly:  true, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:    createContainerName, | ||||
| 						Image:   "gcr.io/google_containers/mounttest:0.7", | ||||
| 						Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/configmap-volumes/create/data-1"}, | ||||
| 						VolumeMounts: []v1.VolumeMount{ | ||||
| 							{ | ||||
| 								Name:      createVolumeName, | ||||
| 								MountPath: path.Join(volumeMountPath, "create"), | ||||
| 								ReadOnly:  true, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				RestartPolicy: v1.RestartPolicyNever, | ||||
| 			}, | ||||
| 		} | ||||
| 		By("Creating the pod") | ||||
| 		f.PodClient().CreateSync(pod) | ||||
|  | ||||
| 		pollCreateLogs := func() (string, error) { | ||||
| 			return framework.GetPodLogs(f.ClientSet, f.Namespace.Name, pod.Name, createContainerName) | ||||
| 		} | ||||
| 		Eventually(pollCreateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("Error reading file /etc/configmap-volumes/create/data-1")) | ||||
|  | ||||
| 		pollUpdateLogs := func() (string, error) { | ||||
| 			return framework.GetPodLogs(f.ClientSet, f.Namespace.Name, pod.Name, updateContainerName) | ||||
| 		} | ||||
| 		Eventually(pollUpdateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("Error reading file /etc/configmap-volumes/update/data-3")) | ||||
|  | ||||
| 		pollDeleteLogs := func() (string, error) { | ||||
| 			return framework.GetPodLogs(f.ClientSet, f.Namespace.Name, pod.Name, deleteContainerName) | ||||
| 		} | ||||
| 		Eventually(pollDeleteLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("value-1")) | ||||
|  | ||||
| 		By(fmt.Sprintf("Deleting configmap %v", deleteConfigMap.Name)) | ||||
| 		err = f.ClientSet.Core().ConfigMaps(f.Namespace.Name).Delete(deleteConfigMap.Name, &v1.DeleteOptions{}) | ||||
| 		Expect(err).NotTo(HaveOccurred(), "Failed to delete configmap %q in namespace %q", deleteConfigMap.Name, f.Namespace.Name) | ||||
|  | ||||
| 		By(fmt.Sprintf("Updating configmap %v", updateConfigMap.Name)) | ||||
| 		updateConfigMap.ResourceVersion = "" // to force update | ||||
| 		delete(updateConfigMap.Data, "data-1") | ||||
| 		updateConfigMap.Data["data-3"] = "value-3" | ||||
| 		_, err = f.ClientSet.Core().ConfigMaps(f.Namespace.Name).Update(updateConfigMap) | ||||
| 		Expect(err).NotTo(HaveOccurred(), "Failed to update configmap %q in namespace %q", updateConfigMap.Name, f.Namespace.Name) | ||||
|  | ||||
| 		By(fmt.Sprintf("Creating configMap with name %s", createConfigMap.Name)) | ||||
| 		if createConfigMap, err = f.ClientSet.Core().ConfigMaps(f.Namespace.Name).Create(createConfigMap); err != nil { | ||||
| 			framework.Failf("unable to create test configMap %s: %v", createConfigMap.Name, err) | ||||
| 		} | ||||
|  | ||||
| 		By("waiting to observe update in volume") | ||||
|  | ||||
| 		Eventually(pollCreateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("value-1")) | ||||
| 		Eventually(pollUpdateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("value-3")) | ||||
| 		Eventually(pollDeleteLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("Error reading file /etc/configmap-volumes/delete/data-1")) | ||||
| 	}) | ||||
|  | ||||
| 	It("should be consumable via environment variable [Conformance]", func() { | ||||
| 		name := "configmap-test-" + string(uuid.NewUUID()) | ||||
| 		configMap := newConfigMap(f, name) | ||||
|   | ||||
| @@ -19,6 +19,8 @@ package common | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"time" | ||||
|  | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/kubernetes/pkg/api/v1" | ||||
| @@ -26,6 +28,7 @@ import ( | ||||
| 	"k8s.io/kubernetes/test/e2e/framework" | ||||
|  | ||||
| 	. "github.com/onsi/ginkgo" | ||||
| 	. "github.com/onsi/gomega" | ||||
| ) | ||||
|  | ||||
| var _ = framework.KubeDescribe("Secrets", func() { | ||||
| @@ -150,6 +153,183 @@ var _ = framework.KubeDescribe("Secrets", func() { | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	It("optional updates should be reflected in volume [Conformance] [Volume]", func() { | ||||
|  | ||||
| 		// We may have to wait or a full sync period to elapse before the | ||||
| 		// Kubelet projects the update into the volume and the container picks | ||||
| 		// it up. This timeout is based on the default Kubelet sync period (1 | ||||
| 		// minute) plus additional time for fudge factor. | ||||
| 		const podLogTimeout = 300 * time.Second | ||||
| 		trueVal := true | ||||
|  | ||||
| 		volumeMountPath := "/etc/secret-volumes" | ||||
|  | ||||
| 		deleteName := "s-test-opt-del-" + string(uuid.NewUUID()) | ||||
| 		deleteContainerName := "dels-volume-test" | ||||
| 		deleteVolumeName := "deletes-volume" | ||||
| 		deleteSecret := &v1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: f.Namespace.Name, | ||||
| 				Name:      deleteName, | ||||
| 			}, | ||||
| 			Data: map[string][]byte{ | ||||
| 				"data-1": []byte("value-1"), | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		updateName := "s-test-opt-upd-" + string(uuid.NewUUID()) | ||||
| 		updateContainerName := "upds-volume-test" | ||||
| 		updateVolumeName := "updates-volume" | ||||
| 		updateSecret := &v1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: f.Namespace.Name, | ||||
| 				Name:      updateName, | ||||
| 			}, | ||||
| 			Data: map[string][]byte{ | ||||
| 				"data-1": []byte("value-1"), | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		createName := "s-test-opt-create-" + string(uuid.NewUUID()) | ||||
| 		createContainerName := "creates-volume-test" | ||||
| 		createVolumeName := "creates-volume" | ||||
| 		createSecret := &v1.Secret{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Namespace: f.Namespace.Name, | ||||
| 				Name:      createName, | ||||
| 			}, | ||||
| 			Data: map[string][]byte{ | ||||
| 				"data-1": []byte("value-1"), | ||||
| 			}, | ||||
| 		} | ||||
|  | ||||
| 		By(fmt.Sprintf("Creating secret with name %s", deleteSecret.Name)) | ||||
| 		var err error | ||||
| 		if deleteSecret, err = f.ClientSet.Core().Secrets(f.Namespace.Name).Create(deleteSecret); err != nil { | ||||
| 			framework.Failf("unable to create test secret %s: %v", deleteSecret.Name, err) | ||||
| 		} | ||||
|  | ||||
| 		By(fmt.Sprintf("Creating secret with name %s", updateSecret.Name)) | ||||
| 		if updateSecret, err = f.ClientSet.Core().Secrets(f.Namespace.Name).Create(updateSecret); err != nil { | ||||
| 			framework.Failf("unable to create test secret %s: %v", updateSecret.Name, err) | ||||
| 		} | ||||
|  | ||||
| 		pod := &v1.Pod{ | ||||
| 			ObjectMeta: metav1.ObjectMeta{ | ||||
| 				Name: "pod-secrets-" + string(uuid.NewUUID()), | ||||
| 			}, | ||||
| 			Spec: v1.PodSpec{ | ||||
| 				Volumes: []v1.Volume{ | ||||
| 					{ | ||||
| 						Name: deleteVolumeName, | ||||
| 						VolumeSource: v1.VolumeSource{ | ||||
| 							Secret: &v1.SecretVolumeSource{ | ||||
| 								SecretName: deleteName, | ||||
| 								Optional:   &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: updateVolumeName, | ||||
| 						VolumeSource: v1.VolumeSource{ | ||||
| 							Secret: &v1.SecretVolumeSource{ | ||||
| 								SecretName: updateName, | ||||
| 								Optional:   &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name: createVolumeName, | ||||
| 						VolumeSource: v1.VolumeSource{ | ||||
| 							Secret: &v1.SecretVolumeSource{ | ||||
| 								SecretName: createName, | ||||
| 								Optional:   &trueVal, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				Containers: []v1.Container{ | ||||
| 					{ | ||||
| 						Name:    deleteContainerName, | ||||
| 						Image:   "gcr.io/google_containers/mounttest:0.7", | ||||
| 						Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/secret-volumes/delete/data-1"}, | ||||
| 						VolumeMounts: []v1.VolumeMount{ | ||||
| 							{ | ||||
| 								Name:      deleteVolumeName, | ||||
| 								MountPath: path.Join(volumeMountPath, "delete"), | ||||
| 								ReadOnly:  true, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:    updateContainerName, | ||||
| 						Image:   "gcr.io/google_containers/mounttest:0.7", | ||||
| 						Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/secret-volumes/update/data-3"}, | ||||
| 						VolumeMounts: []v1.VolumeMount{ | ||||
| 							{ | ||||
| 								Name:      updateVolumeName, | ||||
| 								MountPath: path.Join(volumeMountPath, "update"), | ||||
| 								ReadOnly:  true, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 					{ | ||||
| 						Name:    createContainerName, | ||||
| 						Image:   "gcr.io/google_containers/mounttest:0.7", | ||||
| 						Command: []string{"/mt", "--break_on_expected_content=false", "--retry_time=120", "--file_content_in_loop=/etc/secret-volumes/create/data-1"}, | ||||
| 						VolumeMounts: []v1.VolumeMount{ | ||||
| 							{ | ||||
| 								Name:      createVolumeName, | ||||
| 								MountPath: path.Join(volumeMountPath, "create"), | ||||
| 								ReadOnly:  true, | ||||
| 							}, | ||||
| 						}, | ||||
| 					}, | ||||
| 				}, | ||||
| 				RestartPolicy: v1.RestartPolicyNever, | ||||
| 			}, | ||||
| 		} | ||||
| 		By("Creating the pod") | ||||
| 		f.PodClient().CreateSync(pod) | ||||
|  | ||||
| 		pollCreateLogs := func() (string, error) { | ||||
| 			return framework.GetPodLogs(f.ClientSet, f.Namespace.Name, pod.Name, createContainerName) | ||||
| 		} | ||||
| 		Eventually(pollCreateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("Error reading file /etc/secret-volumes/create/data-1")) | ||||
|  | ||||
| 		pollUpdateLogs := func() (string, error) { | ||||
| 			return framework.GetPodLogs(f.ClientSet, f.Namespace.Name, pod.Name, updateContainerName) | ||||
| 		} | ||||
| 		Eventually(pollUpdateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("Error reading file /etc/secret-volumes/update/data-3")) | ||||
|  | ||||
| 		pollDeleteLogs := func() (string, error) { | ||||
| 			return framework.GetPodLogs(f.ClientSet, f.Namespace.Name, pod.Name, deleteContainerName) | ||||
| 		} | ||||
| 		Eventually(pollDeleteLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("value-1")) | ||||
|  | ||||
| 		By(fmt.Sprintf("Deleting secret %v", deleteSecret.Name)) | ||||
| 		err = f.ClientSet.Core().Secrets(f.Namespace.Name).Delete(deleteSecret.Name, &v1.DeleteOptions{}) | ||||
| 		Expect(err).NotTo(HaveOccurred(), "Failed to delete secret %q in namespace %q", deleteSecret.Name, f.Namespace.Name) | ||||
|  | ||||
| 		By(fmt.Sprintf("Updating secret %v", updateSecret.Name)) | ||||
| 		updateSecret.ResourceVersion = "" // to force update | ||||
| 		delete(updateSecret.Data, "data-1") | ||||
| 		updateSecret.Data["data-3"] = []byte("value-3") | ||||
| 		_, err = f.ClientSet.Core().Secrets(f.Namespace.Name).Update(updateSecret) | ||||
| 		Expect(err).NotTo(HaveOccurred(), "Failed to update secret %q in namespace %q", updateSecret.Name, f.Namespace.Name) | ||||
|  | ||||
| 		By(fmt.Sprintf("Creating secret with name %s", createSecret.Name)) | ||||
| 		if createSecret, err = f.ClientSet.Core().Secrets(f.Namespace.Name).Create(createSecret); err != nil { | ||||
| 			framework.Failf("unable to create test secret %s: %v", createSecret.Name, err) | ||||
| 		} | ||||
|  | ||||
| 		By("waiting to observe update in volume") | ||||
|  | ||||
| 		Eventually(pollCreateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("value-1")) | ||||
| 		Eventually(pollUpdateLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("value-3")) | ||||
| 		Eventually(pollDeleteLogs, podLogTimeout, framework.Poll).Should(ContainSubstring("Error reading file /etc/secret-volumes/delete/data-1")) | ||||
| 	}) | ||||
|  | ||||
| 	It("should be consumable from pods in env vars [Conformance]", func() { | ||||
| 		name := "secret-test-" + string(uuid.NewUUID()) | ||||
| 		secret := secretForTest(f.Namespace.Name, name) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Michael Fraenkel
					Michael Fraenkel