Skip to content

Commit

Permalink
WIP: downward api / env vars
Browse files Browse the repository at this point in the history
  • Loading branch information
pmorie committed Apr 17, 2015
1 parent b3f03b9 commit c0d8fb8
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 16 deletions.
14 changes: 14 additions & 0 deletions pkg/api/serialization_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,20 @@ func TestEncode_Ptr(t *testing.T) {
Spec: api.PodSpec{
RestartPolicy: api.RestartPolicyAlways,
DNSPolicy: api.DNSClusterFirst,
Containers: []api.Container{
{
Env: []api.EnvVar{
{
Name: "foo",
EnvVarSource: &api.EnvVarSource{
HostInfo: &api.HostInfoSource{true},
},
},
},
ImagePullPolicy: api.PullIfNotPresent,
TerminationMessagePath: api.TerminationMessagePathDefault,
},
},
},
}
obj := runtime.Object(pod)
Expand Down
15 changes: 15 additions & 0 deletions pkg/api/testing/fuzzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,21 @@ 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.EnvVarSource = &api.EnvVarSource{}
if c.RandBool() {
ev.EnvVarSource.ObjectInfo = &api.ObjectReference{}
ev.ObjectInfo.APIVersion = c.RandString()
ev.ObjectInfo.FieldPath = c.RandString()
} else {
ev.EnvVarSource.HostInfo = &api.HostInfoSource{true}
}
}
},
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 for the value of the EnvVar.
Source *EnvVarSource `json:"source,omitempty"`
}

// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// ObjectInfo represents a downward API request for a field of the object.
//
// Valid fields of this reference are:
//
// 1. APIVersion
// 2. FieldPath
//
// No other fields may be specified.
//
// Currently ObjectInfo may be used to address the Name and Namespace
// only.
ObjectInfo *ObjectReference `json:"objectInfo,omitempty"`
}

// HTTPGetAction describes an action based on HTTP Get requests.
Expand Down
24 changes: 24 additions & 0 deletions pkg/api/v1beta1/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,18 @@ func init() {
out.Value = in.Value
out.Key = in.Name
out.Name = in.Name

if err := s.Convert(&in.Source, &out.Source, 0); err != nil {
return err
}

return nil
},
func(in *newer.EnvVarSource, out *EnvVarSource, s conversion.Scope) error {
if err := s.Convert(&in.ObjectInfo, &out.ObjectInfo, 0); err != nil {
return err
}

return nil
},
func(in *EnvVar, out *newer.EnvVar, s conversion.Scope) error {
Expand All @@ -125,6 +137,18 @@ func init() {
} else {
out.Name = in.Key
}

if err := s.Convert(&in.Source, &out.Source, 0); err != nil {
return err
}

return nil
},
func(in *EnvVarSource, out *newer.EnvVarSource, s conversion.Scope) error {
if err := s.Convert(&in.ObjectInfo, &out.ObjectInfo, 0); err != nil {
return err
}

return nil
},

Expand Down
19 changes: 19 additions & 0 deletions pkg/api/v1beta1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,25 @@ 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 for the value of the EnvVar.
Source *EnvVarSource `json:"source,omitempty" description:"source for the environment variable's value; cannot be used if value is set"`
}

// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// ObjectInfo represents a downward API request for a field of the object.
//
// Valid fields of this reference are:
//
// 1. APIVersion
// 2. FieldPath
//
// No other fields may be specified.
//
// Currently ObjectInfo may be used to address the Name and Namespace
// only.
ObjectInfo *ObjectReference `json:"objectInfo,omitempty" description:"describes a field of the pod to use the value of; only APIVersion and FieldPath are accepted fields"`
}

// HTTPGetAction describes an action based on HTTP Get requests.
Expand Down
19 changes: 19 additions & 0 deletions pkg/api/v1beta2/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,25 @@ 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 for the value of the EnvVar.
Source *EnvVarSource `json:"source,omitempty" description:"source for the environment variable's value; cannot be used if value is set"`
}

// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// ObjectInfo represents a downward API request for a field of the object.
//
// Valid fields of this reference are:
//
// 1. APIVersion
// 2. FieldPath
//
// No other fields may be specified.
//
// Currently ObjectInfo may be used to address the Name and Namespace
// only.
ObjectInfo *ObjectReference `json:"objectInfo,omitempty" description:"describes a field of the pod to use the value of; only APIVersion and FieldPath are accepted fields"`
}

// HTTPGetAction describes an action based on HTTP Get requests.
Expand Down
1 change: 1 addition & 0 deletions pkg/api/v1beta3/conversion.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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
19 changes: 19 additions & 0 deletions pkg/api/v1beta3/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -519,6 +519,25 @@ 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 for the value of the EnvVar.
Source *EnvVarSource `json:"source,omitempty" description:"source for the environment variable's value; cannot be used if value is set"`
}

// EnvVarSource represents a source for the value of an EnvVar.
// Only one of its members may be specified.
type EnvVarSource struct {
// ObjectInfo represents a downward API request for a field of the object.
//
// Valid fields of this reference are:
//
// 1. APIVersion
// 2. FieldPath
//
// No other fields may be specified.
//
// Currently ObjectInfo may be used to address the Name and Namespace
// only.
ObjectInfo *ObjectReference `json:"objectInfo,omitempty" description:"describes a field of the pod to use the value of; only APIVersion and FieldPath are accepted fields"`
}

// HTTPGetAction describes an action based on HTTP Get requests.
Expand Down
56 changes: 54 additions & 2 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -555,15 +555,67 @@ 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, validateEnvVarSources(ev)...)
allErrs = append(allErrs, vErrs.PrefixIndex(i)...)
}
return allErrs
}

func validateEnvVarSources(ev api.EnvVar) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}

if ev.Source == nil {
return allErrs
}

numSources := 0

switch {
case ev.Source.ObjectInfo != nil:
numSources++
allErrs = append(allErrs, validateObjectInfo(ev.Source.ObjectInfo)...)
}

if ev.Value != "" && numSources >= 1 {
allErrs = append(allErrs, errs.NewFieldInvalid("", "", "sources cannot be specified when value is set"))
} else if numSources > 1 {
allErrs = append(allErrs, errs.NewFieldInvalid("", "", "at most one source can be specified when value is empty"))
}

return allErrs
}

func validateObjectInfo(ref *api.ObjectReference) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}

if ref.FieldPath == "" {
allErrs = append(allErrs, errs.NewFieldRequired("fieldPath"))
}
if ref.APIVersion == "" {
allErrs = append(allErrs, errs.NewFieldRequired("apiVersion"))
}
if ref.Kind != "" {
allErrs = append(allErrs, errs.NewFieldForbidden("kind", ref.Kind))
}
if ref.Namespace != "" {
allErrs = append(allErrs, errs.NewFieldForbidden("namespace", ref.Namespace))
}
if ref.Name != "" {
allErrs = append(allErrs, errs.NewFieldForbidden("name", ref.Name))
}
if ref.UID != "" {
allErrs = append(allErrs, errs.NewFieldForbidden("uid", ref.UID))
}
if ref.ResourceVersion != "" {
allErrs = append(allErrs, errs.NewFieldForbidden("resourceVersion", ref.ResourceVersion))
}

return allErrs
}

func validateVolumeMounts(mounts []api.VolumeMount, volumes util.StringSet) errs.ValidationErrorList {
allErrs := errs.ValidationErrorList{}

Expand Down
98 changes: 89 additions & 9 deletions pkg/api/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,23 +633,103 @@ func TestValidateEnv(t *testing.T) {
{Name: "ABC", Value: "value"},
{Name: "AbC_123", Value: "value"},
{Name: "abc", Value: ""},
{
Name: "abc",
EnvVarSource: &api.EnvVarSource{
ObjectInfo: &api.ObjectReference{
APIVersion: "v1beta3",
FieldPath: "metadata.name",
},
},
},
{
Name: "abc",
EnvVarSource: &api.EnvVarSource{
HostInfo: &api.HostInfoSource{
HostName: true,
},
},
},
}
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 source specified",
envs: []api.EnvVar{{
Name: "abc",
Value: "foo",
EnvVarSource: &api.EnvVarSource{
HostInfo: &api.HostInfoSource{
HostName: true,
},
},
}},
expectedError: "[0]: invalid value '': sources cannot be specified when value is set",
},
{
name: "missing FieldPath on ObjectInfo",
envs: []api.EnvVar{{
Name: "abc",
EnvVarSource: &api.EnvVarSource{
ObjectInfo: &api.ObjectReference{
APIVersion: "v1beta3",
},
},
}},
expectedError: "[0].fieldPath: required value",
},
{
name: "missing APIVersion on ObjectInfo",
envs: []api.EnvVar{{
Name: "abc",
EnvVarSource: &api.EnvVarSource{
ObjectInfo: &api.ObjectReference{
FieldPath: "metadata.name",
},
},
}},
expectedError: "[0].apiVersion: required value",
},
{
name: "forbidden fields on ObjectInfo",
envs: []api.EnvVar{{
Name: "abc",
EnvVarSource: &api.EnvVarSource{
ObjectInfo: &api.ObjectReference{
APIVersion: "v1beta3",
FieldPath: "metadata.name",
UID: "uhoh",
},
},
}},
expectedError: "[0].uid: forbidden 'uhoh'",
},
}
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)
}
}
}
Expand Down
Loading

0 comments on commit c0d8fb8

Please sign in to comment.