Add downward API for environment vars

This commit is contained in:
Paul Morie 2015-04-23 16:57:30 -04:00
parent e0e83fa8fe
commit 7d30f09ebf
21 changed files with 674 additions and 17 deletions

View File

@ -188,6 +188,20 @@ func FuzzerFor(t *testing.T, version string, src rand.Source) *fuzz.Fuzzer {
c.FuzzNoCustom(ct) // fuzz self without calling this function again
ct.TerminationMessagePath = "/" + ct.TerminationMessagePath // Must be non-empty
},
func(ev *api.EnvVar, c fuzz.Continue) {
ev.Name = c.RandString()
if c.RandBool() {
ev.Value = c.RandString()
} else {
ev.ValueFrom = &api.EnvVarSource{}
ev.ValueFrom.FieldPath = &api.ObjectFieldSelector{}
versions := []string{"v1beta1", "v1beta2", "v1beta3"}
ev.ValueFrom.FieldPath.APIVersion = versions[c.Rand.Intn(len(versions))]
ev.ValueFrom.FieldPath.FieldPath = c.RandString()
}
},
func(e *api.Event, c fuzz.Continue) {
c.FuzzNoCustom(e) // fuzz self without calling this function again
// Fix event count to 1, otherwise, if a v1beta1 or v1beta2 event has a count set arbitrarily, it's count is ignored

View File

@ -508,6 +508,25 @@ type EnvVar struct {
Name string `json:"name"`
// Optional: defaults to "".
Value string `json:"value,omitempty"`
// Optional: specify a source the value of this var should come from.
ValueFrom *EnvVarSource `json:"valueFrom,omitempty"`
}
// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// Selects a field of the pod; only name and namespace are supported.
FieldPath *ObjectFieldSelector `json:"fieldPath,omitempty"`
}
// ObjectFieldSelector selects an APIVersioned field of an object.
type ObjectFieldSelector struct {
// The API version the FieldPath is written in terms of.
// If no value is specified, it will be defaulted from the APIVersion
// the enclosing object is created with.
APIVersion string `json:"apiVersion,omitempty"`
// The path of the field to select in the specified API version
FieldPath string `json:"fieldPath,omitempty"`
}
// HTTPGetAction describes an action based on HTTP Get requests.

View File

@ -116,6 +116,11 @@ func init() {
out.Value = in.Value
out.Key = in.Name
out.Name = in.Name
if err := s.Convert(&in.ValueFrom, &out.ValueFrom, 0); err != nil {
return err
}
return nil
},
func(in *EnvVar, out *newer.EnvVar, s conversion.Scope) error {
@ -125,9 +130,13 @@ func init() {
} else {
out.Name = in.Key
}
if err := s.Convert(&in.ValueFrom, &out.ValueFrom, 0); err != nil {
return err
}
return nil
},
// Path & MountType are deprecated.
func(in *newer.VolumeMount, out *VolumeMount, s conversion.Scope) error {
out.Name = in.Name

View File

@ -166,6 +166,11 @@ func init() {
obj.ExternalID = obj.ID
}
},
func(obj *ObjectFieldSelector) {
if obj.APIVersion == "" {
obj.APIVersion = "v1beta1"
}
},
)
}

View File

@ -294,3 +294,29 @@ func TestSetDefaultMinionExternalID(t *testing.T) {
t.Errorf("Expected default External ID: %s, got: %s", name, m2.ExternalID)
}
}
func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) {
s := current.ContainerManifest{
Containers: []current.Container{
{
Env: []current.EnvVar{
{
ValueFrom: &current.EnvVarSource{
FieldPath: &current.ObjectFieldSelector{},
},
},
},
},
},
}
obj2 := roundTrip(t, runtime.Object(&current.ContainerManifestList{
Items: []current.ContainerManifest{s},
}))
sList2 := obj2.(*current.ContainerManifestList)
s2 := sList2.Items[0]
apiVersion := s2.Containers[0].Env[0].ValueFrom.FieldPath.APIVersion
if apiVersion != "v1beta1" {
t.Errorf("Expected default APIVersion v1beta1, got: %v", apiVersion)
}
}

View File

@ -397,6 +397,23 @@ type EnvVar struct {
Key string `json:"key,omitempty" description:"name of the environment variable; must be a C_IDENTIFIER; deprecated - use name instead"`
// Optional: defaults to "".
Value string `json:"value,omitempty" description:"value of the environment variable; defaults to empty string"`
// Optional: specify a source the value of this var should come from.
ValueFrom *EnvVarSource `json:"valueFrom,omitempty" description:"source for the environment variable's value; cannot be used if value is not empty"`
}
// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// Selects a field of the pod; only name and namespace are supported.
FieldPath *ObjectFieldSelector `json:"fieldPath,omitempty" description:"selects a field of the pod; only name and namespace are supported"`
}
// ObjectFieldSelector selects an APIVersioned field of an object.
type ObjectFieldSelector struct {
// The API version the FieldPath is written in terms of.
APIVersion string `json:"apiVersion,omitempty" description="The API version that FieldPath is written in terms of"`
// The path of the field to select in the specified API version
FieldPath string `json:"fieldPath,omitempty" description="The path of the field to select in the specified API version"`
}
// HTTPGetAction describes an action based on HTTP Get requests.

View File

@ -167,6 +167,11 @@ func init() {
obj.ExternalID = obj.ID
}
},
func(obj *ObjectFieldSelector) {
if obj.APIVersion == "" {
obj.APIVersion = "v1beta2"
}
},
)
}

View File

@ -293,3 +293,29 @@ func TestSetDefaultMinionExternalID(t *testing.T) {
t.Errorf("Expected default External ID: %s, got: %s", name, m2.ExternalID)
}
}
func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) {
s := current.ContainerManifest{
Containers: []current.Container{
{
Env: []current.EnvVar{
{
ValueFrom: &current.EnvVarSource{
FieldPath: &current.ObjectFieldSelector{},
},
},
},
},
},
}
obj2 := roundTrip(t, runtime.Object(&current.ContainerManifestList{
Items: []current.ContainerManifest{s},
}))
sList2 := obj2.(*current.ContainerManifestList)
s2 := sList2.Items[0]
apiVersion := s2.Containers[0].Env[0].ValueFrom.FieldPath.APIVersion
if apiVersion != "v1beta2" {
t.Errorf("Expected default APIVersion v1beta2, got: %v", apiVersion)
}
}

View File

@ -381,6 +381,23 @@ type EnvVar struct {
Name string `json:"name" description:"name of the environment variable; must be a C_IDENTIFIER"`
// Optional: defaults to "".
Value string `json:"value,omitempty" description:"value of the environment variable; defaults to empty string"`
// Optional: specify a source the value of this var should come from.
ValueFrom *EnvVarSource `json:"valueFrom,omitempty" description:"source for the environment variable's value; cannot be used if value is not empty"`
}
// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// Selects a field of the pod; only name and namespace are supported.
FieldPath *ObjectFieldSelector `json:"fieldPath,omitempty" description:"selects a field of the pod; only name and namespace are supported"`
}
// ObjectFieldSelector selects an APIVersioned field of an object.
type ObjectFieldSelector struct {
// The API version the FieldPath is written in terms of.
APIVersion string `json:"apiVersion,omitempty" description="The API version that FieldPath is written in terms of"`
// The path of the field to select in the specified API version
FieldPath string `json:"fieldPath,omitempty" description="The path of the field to select in the specified API version"`
}
// HTTPGetAction describes an action based on HTTP Get requests.

View File

@ -1794,6 +1794,23 @@ func init() {
out.Path = in.Path
return nil
},
func(in *EnvVar, out *newer.EnvVar, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
if err := s.Convert(&in.ValueFrom, &out.ValueFrom, 0); err != nil {
return err
}
return nil
},
func(in *newer.EnvVar, out *EnvVar, s conversion.Scope) error {
out.Name = in.Name
out.Value = in.Value
if err := s.Convert(&in.ValueFrom, &out.ValueFrom, 0); err != nil {
return err
}
return nil
},
func(in *PodSpec, out *newer.PodSpec, s conversion.Scope) error {
if in.Volumes != nil {
out.Volumes = make([]newer.Volume, len(in.Volumes))
@ -2715,6 +2732,7 @@ func init() {
func(label, value string) (string, string, error) {
switch label {
case "metadata.name",
"metadata.namespace",
"status.phase",
"spec.host":
return label, value, nil

View File

@ -128,6 +128,11 @@ func init() {
obj.Spec.ExternalID = obj.Name
}
},
func(obj *ObjectFieldSelector) {
if obj.APIVersion == "" {
obj.APIVersion = "v1beta3"
}
},
)
}

View File

@ -302,3 +302,30 @@ func TestSetDefaultNodeExternalID(t *testing.T) {
t.Errorf("Expected default External ID: %s, got: %s", name, n2.Spec.ExternalID)
}
}
func TestSetDefaultObjectFieldSelectorAPIVersion(t *testing.T) {
s := current.PodSpec{
Containers: []current.Container{
{
Env: []current.EnvVar{
{
ValueFrom: &current.EnvVarSource{
FieldPath: &current.ObjectFieldSelector{},
},
},
},
},
},
}
pod := &current.Pod{
Spec: s,
}
obj2 := roundTrip(t, runtime.Object(pod))
pod2 := obj2.(*current.Pod)
s2 := pod2.Spec
apiVersion := s2.Containers[0].Env[0].ValueFrom.FieldPath.APIVersion
if apiVersion != "v1beta3" {
t.Errorf("Expected default APIVersion v1beta3, got: %v", apiVersion)
}
}

View File

@ -519,6 +519,23 @@ type EnvVar struct {
Name string `json:"name" description:"name of the environment variable; must be a C_IDENTIFIER"`
// Optional: defaults to "".
Value string `json:"value,omitempty" description:"value of the environment variable; defaults to empty string"`
// Optional: specify a source the value of this var should come from.
ValueFrom *EnvVarSource `json:"valueFrom,omitempty" description:"source for the environment variable's value; cannot be used if value is not empty"`
}
// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// Selects a field of the pod; only name and namespace are supported.
FieldPath *ObjectFieldSelector `json:"fieldPath,omitempty" description:"selects a field of the pod; only name and namespace are supported"`
}
// ObjectFieldSelector selects an APIVersioned field of an object.
type ObjectFieldSelector struct {
// The API version the FieldPath is written in terms of.
APIVersion string `json:"apiVersion,omitempty" description="The API version that FieldPath is written in terms of"`
// The path of the field to select in the specified API version
FieldPath string `json:"fieldPath,omitempty" description="The path of the field to select in the specified API version"`
}
// HTTPGetAction describes an action based on HTTP Get requests.

View File

@ -555,15 +555,58 @@ func validateEnv(vars []api.EnvVar) errs.ValidationErrorList {
vErrs := errs.ValidationErrorList{}
if len(ev.Name) == 0 {
vErrs = append(vErrs, errs.NewFieldRequired("name"))
}
if !util.IsCIdentifier(ev.Name) {
} else if !util.IsCIdentifier(ev.Name) {
vErrs = append(vErrs, errs.NewFieldInvalid("name", ev.Name, cIdentifierErrorMsg))
}
vErrs = append(vErrs, validateEnvVarValueFrom(ev).Prefix("valueFrom")...)
allErrs = append(allErrs, vErrs.PrefixIndex(i)...)
}
return allErrs
}
func validateEnvVarValueFrom(ev api.EnvVar) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if ev.ValueFrom == nil {
return allErrs
}
numSources := 0
switch {
case ev.ValueFrom.FieldPath != nil:
numSources++
allErrs = append(allErrs, validateObjectFieldSelector(ev.ValueFrom.FieldPath).Prefix("fieldPath")...)
}
if ev.Value != "" && numSources != 0 {
allErrs = append(allErrs, errs.NewFieldInvalid("", "", "sources cannot be specified when value is not empty"))
}
return allErrs
}
var validFieldPathExpressions = util.NewStringSet("metadata.name", "metadata.namespace")
func validateObjectFieldSelector(fs *api.ObjectFieldSelector) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}
if fs.APIVersion == "" {
allErrs = append(allErrs, errs.NewFieldRequired("apiVersion"))
} else if fs.FieldPath == "" {
allErrs = append(allErrs, errs.NewFieldRequired("fieldPath"))
} else {
internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "")
if err != nil {
allErrs = append(allErrs, errs.NewFieldInvalid("fieldPath", fs.FieldPath, "error converting fieldPath"))
} else if !validFieldPathExpressions.Has(internalFieldPath) {
allErrs = append(allErrs, errs.NewFieldNotSupported("fieldPath", internalFieldPath))
}
}
return allErrs
}
func validateVolumeMounts(mounts []api.VolumeMount, volumes util.StringSet) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}

View File

@ -633,23 +633,108 @@ func TestValidateEnv(t *testing.T) {
{Name: "ABC", Value: "value"},
{Name: "AbC_123", Value: "value"},
{Name: "abc", Value: ""},
{
Name: "abc",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
APIVersion: "v1beta3",
FieldPath: "metadata.name",
},
},
},
}
if errs := validateEnv(successCase); len(errs) != 0 {
t.Errorf("expected success: %v", errs)
}
errorCases := map[string][]api.EnvVar{
"zero-length name": {{Name: ""}},
"name not a C identifier": {{Name: "a.b.c"}},
errorCases := []struct {
name string
envs []api.EnvVar
expectedError string
}{
{
name: "zero-length name",
envs: []api.EnvVar{{Name: ""}},
expectedError: "[0].name: required value",
},
{
name: "name not a C identifier",
envs: []api.EnvVar{{Name: "a.b.c"}},
expectedError: "[0].name: invalid value 'a.b.c': must match regex [A-Za-z_][A-Za-z0-9_]*",
},
{
name: "value and valueFrom specified",
envs: []api.EnvVar{{
Name: "abc",
Value: "foo",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
APIVersion: "v1beta3",
FieldPath: "metadata.name",
},
},
}},
expectedError: "[0].valueFrom: invalid value '': sources cannot be specified when value is not empty",
},
{
name: "missing FieldPath on ObjectFieldSelector",
envs: []api.EnvVar{{
Name: "abc",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
APIVersion: "v1beta3",
},
},
}},
expectedError: "[0].valueFrom.fieldPath.fieldPath: required value",
},
{
name: "missing APIVersion on ObjectFieldSelector",
envs: []api.EnvVar{{
Name: "abc",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
}},
expectedError: "[0].valueFrom.fieldPath.apiVersion: required value",
},
{
name: "invalid fieldPath",
envs: []api.EnvVar{{
Name: "abc",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
FieldPath: "metadata.whoops",
APIVersion: "v1beta3",
},
},
}},
expectedError: "[0].valueFrom.fieldPath.fieldPath: invalid value 'metadata.whoops': error converting fieldPath",
},
{
name: "unsupported fieldPath",
envs: []api.EnvVar{{
Name: "abc",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
FieldPath: "status.phase",
APIVersion: "v1beta3",
},
},
}},
expectedError: "[0].valueFrom.fieldPath.fieldPath: unsupported value 'status.phase'",
},
}
for k, v := range errorCases {
if errs := validateEnv(v); len(errs) == 0 {
t.Errorf("expected failure for %s", k)
for _, tc := range errorCases {
if errs := validateEnv(tc.envs); len(errs) == 0 {
t.Errorf("expected failure for %s", tc.name)
} else {
for i := range errs {
detail := errs[i].(*errors.ValidationError).Detail
if detail != "" && detail != cIdentifierErrorMsg {
t.Errorf("%s: expected error detail either empty or %s, got %s", k, cIdentifierErrorMsg, detail)
str := errs[i].(*errors.ValidationError).Error()
if str != "" && str != tc.expectedError {
t.Errorf("%s: expected error detail either empty or %s, got %s", tc.name, tc.expectedError, str)
}
}
}

19
pkg/fieldpath/doc.go Normal file
View File

@ -0,0 +1,19 @@
/*
Copyright 2015 Google Inc. 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 fieldpath supplies methods for extracting fields from objects
// given a path to a field.
package fieldpath

View File

@ -0,0 +1,47 @@
/*
Copyright 2015 Google Inc. 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 fieldpath
import (
"fmt"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
)
// ExtractFieldPathAsString extracts the field from the given object
// and returns it as a string. The object must be a pointer to an
// API type.
//
// Currently, this API is limited to supporting the fieldpaths:
//
// 1. metadata.name - The name of an API object
// 2. metadata.namespace - The namespace of an API object
func ExtractFieldPathAsString(obj interface{}, fieldPath string) (string, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return "", nil
}
switch fieldPath {
case "metadata.name":
return accessor.Name(), nil
case "metadata.namespace":
return accessor.Namespace(), nil
}
return "", fmt.Errorf("Unsupported fieldPath: %v", fieldPath)
}

View File

@ -0,0 +1,86 @@
/*
Copyright 2015 Google Inc. 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 fieldpath
import (
"strings"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
)
func TestExtractFieldPathAsString(t *testing.T) {
cases := []struct {
name string
fieldPath string
obj interface{}
expectedValue string
expectedMessageFragment string
}{
{
name: "not an API object",
fieldPath: "metadata.name",
obj: "",
expectedMessageFragment: "expected struct",
},
{
name: "ok - namespace",
fieldPath: "metadata.namespace",
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Namespace: "object-namespace",
},
},
expectedValue: "object-namespace",
},
{
name: "ok - name",
fieldPath: "metadata.name",
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: "object-name",
},
},
expectedValue: "object-name",
},
{
name: "invalid expression",
fieldPath: "metadata.whoops",
obj: &api.Pod{
ObjectMeta: api.ObjectMeta{
Namespace: "object-namespace",
},
},
expectedMessageFragment: "Unsupported fieldPath",
},
}
for _, tc := range cases {
actual, err := ExtractFieldPathAsString(tc.obj, tc.fieldPath)
if err != nil {
if tc.expectedMessageFragment != "" {
if !strings.Contains(err.Error(), tc.expectedMessageFragment) {
t.Errorf("%v: Unexpected error message: %q, expected to contain %q", tc.name, err, tc.expectedMessageFragment)
}
} else {
t.Errorf("%v: unexpected error: %v", tc.name, err)
}
} else if e := tc.expectedValue; e != "" && e != actual {
t.Errorf("%v: Unexpected result; got %q, expected %q", tc.name, actual, e)
}
}
}

View File

@ -36,6 +36,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client/record"
"github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fieldpath"
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/cadvisor"
kubecontainer "github.com/GoogleCloudPlatform/kubernetes/pkg/kubelet/container"
@ -706,7 +707,7 @@ func (kl *Kubelet) GenerateRunContainerOptions(pod *api.Pod, container *api.Cont
return nil, fmt.Errorf("impossible: cannot find the mounted volumes for pod %q", kubecontainer.GetPodFullName(pod))
}
opts.Binds = makeBinds(container, vol)
opts.Envs, err = kl.makeEnvironmentVariables(pod.Namespace, container)
opts.Envs, err = kl.makeEnvironmentVariables(pod, container)
if err != nil {
return nil, err
}
@ -784,7 +785,7 @@ func (kl *Kubelet) getServiceEnvVarMap(ns string) (map[string]string, error) {
}
// Make the service environment variables for a pod in the given namespace.
func (kl *Kubelet) makeEnvironmentVariables(ns string, container *api.Container) ([]string, error) {
func (kl *Kubelet) makeEnvironmentVariables(pod *api.Pod, container *api.Container) ([]string, error) {
var result []string
// Note: These are added to the docker.Config, but are not included in the checksum computed
// by dockertools.BuildDockerName(...). That way, we can still determine whether an
@ -795,7 +796,7 @@ func (kl *Kubelet) makeEnvironmentVariables(ns string, container *api.Container)
// To avoid this users can: (1) wait between starting a service and starting; or (2) detect
// missing service env var and exit and be restarted; or (3) use DNS instead of env vars
// and keep trying to resolve the DNS name of the service (recommended).
serviceEnv, err := kl.getServiceEnvVarMap(ns)
serviceEnv, err := kl.getServiceEnvVarMap(pod.Namespace)
if err != nil {
return result, err
}
@ -807,7 +808,13 @@ func (kl *Kubelet) makeEnvironmentVariables(ns string, container *api.Container)
// env vars.
// TODO: remove this net line once all platforms use apiserver+Pods.
delete(serviceEnv, value.Name)
result = append(result, fmt.Sprintf("%s=%s", value.Name, value.Value))
runtimeValue, err := kl.runtimeEnvVarValue(value, pod)
if err != nil {
return result, err
}
result = append(result, fmt.Sprintf("%s=%s", value.Name, runtimeValue))
}
// Append remaining service env vars.
@ -817,6 +824,33 @@ func (kl *Kubelet) makeEnvironmentVariables(ns string, container *api.Container)
return result, nil
}
// runtimeEnvVarValue determines the value that an env var should take when a container
// is started. If the value of the env var is the empty string, the source of the env var
// is resolved, if one is specified.
//
// TODO: preliminary factoring; make better
func (kl *Kubelet) runtimeEnvVarValue(envVar api.EnvVar, pod *api.Pod) (string, error) {
runtimeVal := envVar.Value
if runtimeVal != "" {
return runtimeVal, nil
}
if envVar.ValueFrom != nil && envVar.ValueFrom.FieldPath != nil {
return kl.podFieldSelectorRuntimeValue(envVar.ValueFrom.FieldPath, pod)
}
return runtimeVal, nil
}
func (kl *Kubelet) podFieldSelectorRuntimeValue(fs *api.ObjectFieldSelector, pod *api.Pod) (string, error) {
internalFieldPath, _, err := api.Scheme.ConvertFieldLabel(fs.APIVersion, "Pod", fs.FieldPath, "")
if err != nil {
return "", err
}
return fieldpath.ExtractFieldPathAsString(pod, internalFieldPath)
}
// getClusterDNS returns a list of the DNS servers and a list of the DNS search
// domains of the cluster.
func (kl *Kubelet) getClusterDNS(pod *api.Pod) ([]string, []string, error) {

View File

@ -68,6 +68,8 @@ type TestKubelet struct {
fakeMirrorClient *fakeMirrorClient
}
const testKubeletHostname = "testnode"
func newTestKubelet(t *testing.T) *TestKubelet {
fakeDocker := &dockertools.FakeDockerClient{Errors: make(map[string]error), RemovedImages: util.StringSet{}}
fakeRecorder := &record.FakeRecorder{}
@ -2378,6 +2380,39 @@ func TestMakeEnvironmentVariables(t *testing.T) {
"KUBERNETES_RO_PORT_8087_TCP_ADDR=1.2.3.7"),
21,
},
{
"downward api pod",
"downward-api",
&api.Container{
Env: []api.EnvVar{
{
Name: "POD_NAME",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
APIVersion: "v1beta3",
FieldPath: "metadata.name",
},
},
},
{
Name: "POD_NAMESPACE",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
APIVersion: "v1beta3",
FieldPath: "metadata.namespace",
},
},
},
},
},
"nothing",
true,
util.NewStringSet(
"POD_NAME=dapi-test-pod-name",
"POD_NAMESPACE=downward-api",
),
2,
},
}
for _, tc := range testCases {
@ -2390,7 +2425,14 @@ func TestMakeEnvironmentVariables(t *testing.T) {
kl.serviceLister = testServiceLister{services}
}
result, err := kl.makeEnvironmentVariables(tc.ns, tc.container)
testPod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Namespace: tc.ns,
Name: "dapi-test-pod-name",
},
}
result, err := kl.makeEnvironmentVariables(testPod, tc.container)
if err != nil {
t.Errorf("[%v] Unexpected error: %v", tc.name, err)
}

96
test/e2e/downward_api.go Normal file
View File

@ -0,0 +1,96 @@
/*
Copyright 2015 Google Inc. 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"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Downward API", func() {
var c *client.Client
var ns string
BeforeEach(func() {
var err error
c, err = loadClient()
Expect(err).NotTo(HaveOccurred())
ns_, err := createTestingNS("downward-api", c)
ns = ns_.Name
Expect(err).NotTo(HaveOccurred())
})
AfterEach(func() {
// Clean up the namespace if a non-default one was used
if ns != api.NamespaceDefault {
By("Cleaning up the namespace")
err := c.Namespaces().Delete(ns)
expectNoError(err)
}
})
It("should provide pod name and namespace as env vars", func() {
podName := "downward-api-" + string(util.NewUUID())
pod := &api.Pod{
ObjectMeta: api.ObjectMeta{
Name: podName,
Labels: map[string]string{"name": podName},
},
Spec: api.PodSpec{
Containers: []api.Container{
{
Name: "dapi-container",
Image: "gcr.io/google_containers/busybox",
Command: []string{"sh", "-c", "env"},
Env: []api.EnvVar{
{
Name: "POD_NAME",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
APIVersion: "v1beta3",
FieldPath: "metadata.name",
},
},
},
{
Name: "POD_NAMESPACE",
ValueFrom: &api.EnvVarSource{
FieldPath: &api.ObjectFieldSelector{
APIVersion: "v1beta3",
FieldPath: "metadata.namespace",
},
},
},
},
},
},
RestartPolicy: api.RestartPolicyNever,
},
}
testContainerOutputInNamespace("downward api env vars", c, pod, []string{
fmt.Sprintf("POD_NAME=%v", podName),
fmt.Sprintf("POD_NAMESPACE=%v", ns),
}, ns)
})
})