Skip to content

Commit

Permalink
Add downward API for environment vars
Browse files Browse the repository at this point in the history
  • Loading branch information
pmorie committed Apr 27, 2015
1 parent e0e83fa commit 7d30f09
Show file tree
Hide file tree
Showing 21 changed files with 674 additions and 17 deletions.
14 changes: 14 additions & 0 deletions pkg/api/testing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions pkg/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
11 changes: 10 additions & 1 deletion pkg/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/v1beta1/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,11 @@ func init() {
obj.ExternalID = obj.ID
}
},
func(obj *ObjectFieldSelector) {
if obj.APIVersion == "" {
obj.APIVersion = "v1beta1"
}
},
)
}

Expand Down
26 changes: 26 additions & 0 deletions pkg/api/v1beta1/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
17 changes: 17 additions & 0 deletions pkg/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/v1beta2/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ func init() {
obj.ExternalID = obj.ID
}
},
func(obj *ObjectFieldSelector) {
if obj.APIVersion == "" {
obj.APIVersion = "v1beta2"
}
},
)
}

Expand Down
26 changes: 26 additions & 0 deletions pkg/api/v1beta2/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
17 changes: 17 additions & 0 deletions pkg/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
18 changes: 18 additions & 0 deletions pkg/api/v1beta3/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions pkg/api/v1beta3/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,11 @@ func init() {
obj.Spec.ExternalID = obj.Name
}
},
func(obj *ObjectFieldSelector) {
if obj.APIVersion == "" {
obj.APIVersion = "v1beta3"
}
},
)
}

Expand Down
27 changes: 27 additions & 0 deletions pkg/api/v1beta3/defaults_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
17 changes: 17 additions & 0 deletions pkg/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
47 changes: 45 additions & 2 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}

Expand Down
Loading

0 comments on commit 7d30f09

Please sign in to comment.