diff --git a/pkg/api/types.go b/pkg/api/types.go index bb81f2f9510..80090334935 100644 --- a/pkg/api/types.go +++ b/pkg/api/types.go @@ -715,9 +715,12 @@ type EnvVar struct { } // EnvVarSource represents a source for the value of an EnvVar. +// Only one of its fields may be set. type EnvVarSource struct { - // Required: Selects a field of the pod; only name and namespace are supported. - FieldRef *ObjectFieldSelector `json:"fieldRef"` + // Selects a field of the pod; only name and namespace are supported. + FieldRef *ObjectFieldSelector `json:"fieldRef,omitempty"` + // Selects a key of a ConfigMap. + ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` } // ObjectFieldSelector selects an APIVersioned field of an object. @@ -730,6 +733,14 @@ type ObjectFieldSelector struct { FieldPath string `json:"fieldPath"` } +// Selects a key from a ConfigMap. +type ConfigMapKeySelector struct { + // The ConfigMap to select from. + LocalObjectReference `json:",inline"` + // The key to select. + Key string `json:"key"` +} + // HTTPGetAction describes an action based on HTTP Get requests. type HTTPGetAction struct { // Optional: Path to access on the HTTP server. diff --git a/pkg/api/v1/types.go b/pkg/api/v1/types.go index cdc8dce9b38..9b0cfe5df09 100644 --- a/pkg/api/v1/types.go +++ b/pkg/api/v1/types.go @@ -843,8 +843,10 @@ type EnvVar struct { // EnvVarSource represents a source for the value of an EnvVar. type EnvVarSource struct { - // Selects a field of the pod. Only name and namespace are supported. - FieldRef *ObjectFieldSelector `json:"fieldRef"` + // Selects a field of the pod; only name and namespace are supported. + FieldRef *ObjectFieldSelector `json:"fieldRef,omitempty"` + // Selects a key of a ConfigMap. + ConfigMapKeyRef *ConfigMapKeySelector `json:"configMapKeyRef,omitempty"` } // ObjectFieldSelector selects an APIVersioned field of an object. @@ -855,6 +857,14 @@ type ObjectFieldSelector struct { FieldPath string `json:"fieldPath"` } +// Selects a key from a ConfigMap. +type ConfigMapKeySelector struct { + // The ConfigMap to select from. + LocalObjectReference `json:",inline"` + // The key to select. + Key string `json:"key"` +} + // HTTPGetAction describes an action based on HTTP Get requests. type HTTPGetAction struct { // Path to access on the HTTP server. diff --git a/pkg/api/validation/validation.go b/pkg/api/validation/validation.go index 997105f7ecf..165c3b2914e 100644 --- a/pkg/api/validation/validation.go +++ b/pkg/api/validation/validation.go @@ -968,6 +968,9 @@ func validateEnvVarValueFrom(ev api.EnvVar, fldPath *field.Path) field.ErrorList case ev.ValueFrom.FieldRef != nil: numSources++ allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldRef, &validFieldPathExpressionsEnv, fldPath.Child("fieldRef"))...) + case ev.ValueFrom.ConfigMapKeyRef != nil: + numSources++ + allErrs = append(allErrs, validateConfigMapKeySelector(ev.ValueFrom.ConfigMapKeyRef, fldPath.Child("configMapKeyRef"))...) } if len(ev.Value) != 0 && numSources != 0 { @@ -996,6 +999,21 @@ func validateObjectFieldSelector(fs *api.ObjectFieldSelector, expressions *sets. return allErrs } +func validateConfigMapKeySelector(s *api.ConfigMapKeySelector, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + if len(s.Name) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("name"), "")) + } + if len(s.Key) == 0 { + allErrs = append(allErrs, field.Required(fldPath.Child("key"), "")) + } else if !IsSecretKey(s.Key) { + allErrs = append(allErrs, field.Invalid(fldPath.Child("key"), s.Key, fmt.Sprintf("must have at most %d characters and match regex %s", validation.DNS1123SubdomainMaxLength, SecretKeyFmt))) + } + + return allErrs +} + func validateVolumeMounts(mounts []api.VolumeMount, volumes sets.String, fldPath *field.Path) field.ErrorList { allErrs := field.ErrorList{} diff --git a/pkg/kubelet/kubelet.go b/pkg/kubelet/kubelet.go index 713748a91ad..d85f8c869f3 100644 --- a/pkg/kubelet/kubelet.go +++ b/pkg/kubelet/kubelet.go @@ -40,6 +40,7 @@ import ( "k8s.io/kubernetes/pkg/api/resource" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" + apiextensions "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/cache" "k8s.io/kubernetes/pkg/client/record" client "k8s.io/kubernetes/pkg/client/unversioned" @@ -1391,9 +1392,11 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Contain // b. If a source is defined for an environment variable, resolve the source // 2. Create the container's environment in the order variables are declared // 3. Add remaining service environment vars - - tmpEnv := make(map[string]string) - mappingFunc := expansion.MappingFuncFor(tmpEnv, serviceEnv) + var ( + tmpEnv = make(map[string]string) + configMaps = make(map[string]*apiextensions.ConfigMap) + mappingFunc = expansion.MappingFuncFor(tmpEnv, serviceEnv) + ) for _, envVar := range container.Env { // Accesses apiserver+Pods. // So, the master may set service env vars, or kubelet may. In case both are doing @@ -1406,11 +1409,28 @@ func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Contain if runtimeVal != "" { // Step 1a: expand variable references runtimeVal = expansion.Expand(runtimeVal, mappingFunc) - } else if envVar.ValueFrom != nil && envVar.ValueFrom.FieldRef != nil { + } else if envVar.ValueFrom != nil { // Step 1b: resolve alternate env var sources - runtimeVal, err = kl.podFieldSelectorRuntimeValue(envVar.ValueFrom.FieldRef, pod) - if err != nil { - return result, err + switch { + case envVar.ValueFrom.FieldRef != nil: + runtimeVal, err = kl.podFieldSelectorRuntimeValue(envVar.ValueFrom.FieldRef, pod) + if err != nil { + return result, err + } + case envVar.ValueFrom.ConfigMapKeyRef != nil: + name := envVar.ValueFrom.ConfigMapKeyRef.Name + key := envVar.ValueFrom.ConfigMapKeyRef.Key + configMap, ok := configMaps[name] + if !ok { + configMap, err = kl.kubeClient.Extensions().ConfigMaps(pod.Namespace).Get(name) + if err != nil { + return result, err + } + } + runtimeVal, ok = configMap.Data[key] + if !ok { + return result, fmt.Errorf("Couldn't find key %v in ConfigMap %v/%v", key, pod.Namespace, name) + } } } diff --git a/test/e2e/configmap.go b/test/e2e/configmap.go new file mode 100644 index 00000000000..83bca6fab1c --- /dev/null +++ b/test/e2e/configmap.go @@ -0,0 +1,91 @@ +/* +Copyright 2014 The Kubernetes Authors All rights reserved. + +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 e2e + +import ( + "fmt" + + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/apis/extensions" + "k8s.io/kubernetes/pkg/util" + + . "github.com/onsi/ginkgo" +) + +var _ = Describe("ConfigMap", func() { + f := NewFramework("configmap") + + It("should be consumable via environment variable [Conformance]", func() { + name := "configmap-test-" + string(util.NewUUID()) + configMap := &extensions.ConfigMap{ + ObjectMeta: api.ObjectMeta{ + Namespace: f.Namespace.Name, + Name: name, + }, + Data: map[string]string{ + "data-1": "value-1", + "data-2": "value-2", + "data-3": "value-3", + }, + } + + By(fmt.Sprintf("Creating configMap %v/%v", f.Namespace.Name, configMap.Name)) + defer func() { + By("Cleaning up the configMap") + if err := f.Client.Extensions().ConfigMaps(f.Namespace.Name).Delete(configMap.Name); err != nil { + Failf("unable to delete configMap %v: %v", configMap.Name, err) + } + }() + var err error + if configMap, err = f.Client.Extensions().ConfigMaps(f.Namespace.Name).Create(configMap); err != nil { + Failf("unable to create test configMap %s: %v", configMap.Name, err) + } + + pod := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "pod-configmaps-" + string(util.NewUUID()), + }, + Spec: api.PodSpec{ + Containers: []api.Container{ + { + Name: "env-test", + Image: "gcr.io/google_containers/busybox", + Command: []string{"sh", "-c", "env"}, + Env: []api.EnvVar{ + { + Name: "CONFIG_DATA_1", + ValueFrom: &api.EnvVarSource{ + ConfigMapKeyRef: &api.ConfigMapKeySelector{ + LocalObjectReference: api.LocalObjectReference{ + Name: name, + }, + Key: "data-1", + }, + }, + }, + }, + }, + }, + RestartPolicy: api.RestartPolicyNever, + }, + } + + testContainerOutput("consume configMaps", f.Client, pod, 0, []string{ + "CONFIG_DATA_1=value-1", + }, f.Namespace.Name) + }) +})