Add support for ConfigMap keys in env vars

This commit is contained in:
Paul Morie 2015-12-17 15:51:51 -05:00
parent c059dfdb9b
commit 6cfd101251
5 changed files with 161 additions and 11 deletions

View File

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

View File

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

View File

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

View File

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

91
test/e2e/configmap.go Normal file
View File

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