Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Env var sources / downward API #6739

Merged
merged 1 commit into from
Apr 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you confirm that this isn't called when the pointer is nil?

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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fine for now - it's not your fault/problem, but I want to be on record that asking the user to spec an API version inside an object that is already API-versioned is asinine. We should endeavor to do better than this in API v2.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Apr 21, 2015, at 6:28 PM, Tim Hockin notifications@github.com wrote:

In pkg/api/v1beta1/types.go:

@@ -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 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 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.
  • PodField *ObjectFieldSelector json:"podField,omitempty" description:"selectes 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"
    This is fine for now - it's not your fault/problem, but I want to be on record that asking the user to spec an API version inside an object that is already API-versioned is asinine. We should endeavor to do better than this in API v2.

All we have to do is default it in the conversion. We just don't have a cheap way of doing that.

Reply to this email directly or view it on GitHub.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem as ObjectReference, validation errors, field selectors, and other field references.

Sorry I missed the intervening discussion (too much going on), but podField and ObjectFieldSelector seem inelegant. ObjectReference was sufficient and more extensible. I understand that it seems overly general right now, but if we wanted more specific, I'd rather scrap both EnvVarSource and ObjectFieldSelector and just put ValueFromField in EnvVar.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

regarding ObjectRef vs ObjectFieldSelector, here's my thinking.

It is a strict subset of ObejctRef. We should not reuse struct just because it exists. If it grows to be identical to ObjectRef, we can swap in that struct at will, and it will be compatible. We would not add fields to a struct just because we might need them. I feel we should be doing this MORE - use purpose-built structs that fit exactly what we need. The struct name is not really even part of the API.

That's the last I'll say, if Brian wants to use Objectref, just do that

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bgrant0607 is ValueFromField a string?
On Wed, Apr 22, 2015 at 12:30 AM Brian Grant notifications@github.com
wrote:

In pkg/api/v1beta1/types.go
#6739 (comment)
:

@@ -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 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 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.
  • PodField *ObjectFieldSelector json:"podField,omitempty" description:"selectes 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"

Same problem as ObjectReference, validation errors, field selectors, and
other field references.

Sorry I missed the intervening discussion (too much going on), but
podField and ObjectFieldSelector seem inelegant. ObjectReference was
sufficient and more extensible. I understand that it seems overly general
right now, but if we wanted more specific, I'd rather scrap both
EnvVarSource and ObjectFieldSelector and just put ValueFromField in EnvVar.


Reply to this email directly or view it on GitHub
https://github.com/GoogleCloudPlatform/kubernetes/pull/6739/files#r28843747
.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want to use ObjectFieldSelector, document that it must be a strict subset of ObjectReference.

My bigger beef is with the field name: podField. I'm 99% certain that we're going to need to be able to substitute fields of other objects. In the case that we support that in the API, podField is awkward. In the case that we push cross-object substitution to the client, podField seems both over- and under-specified (of course it must be a pod, but it can't be another pod).

I'll try to provide a more constructive suggestion later tonight.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the argument against just "field"?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

description nits: Delete leading "The" and refer to the json field names, not the Go struct names (so, fieldPath rather than FieldPath).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping (here and other API versions)

// 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"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to document the format of this somewhere

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to documentation. For starters, an example in the description (e.g., "metadata.name") would be useful.

Delete leading "The".

FieldPath isn't really optional.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ping (here and other API versions)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gah, description=

}

// 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",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm resigned to the fact that starting with this syntax is the most expedient thing to do. We can reevaluate later.

In the long run, we should strive to make all field references the same:

  • downward API substitution
  • client-side cross-object field references
  • kubectl -o template
  • field selectors
  • ObjectReference
  • field projection (only return a subset of object fields)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forget I said anything. :-) I'd like to get this in.

"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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At what point do we validate these are legitimate values?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@thockin At this point, that happens in the kubelet. I don't think that's great. Let me think on this a bit more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems totally feasible to validate them in apiserver even if expanded by Kubelet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On Apr 22, 2015, at 9:30 PM, Brian Grant notifications@github.com wrote:

In pkg/api/validation/validation.go:

  • numSources := 0
  • switch {
  • case ev.Source.PodField != nil:
  •   numSources++
    
  •   allErrs = append(allErrs, validateObjectFieldSelector(ev.Source.PodField).Prefix("podField")...)
    
  • }
  • if ev.Value != "" && numSources != 0 {
  •   allErrs = append(allErrs, errs.NewFieldInvalid("", "", "sources cannot be specified when value is not empty"))
    
  • }
  • return allErrs
    +}

+func validateObjectFieldSelector(fs *api.ObjectFieldSelector) errs.ValidationErrorList {
Seems totally feasible to validate them in apiserver even if expanded by Kubelet.

So if an network plugin was running on the nodes and could offer downward API to pods (not saying I have a concrete scenario in mind), the apiserver would need to know about the full shape of the downward API object the network plugin exposes? Just trying to clarify what we mean by validation.

Reply to this email directly or view it on GitHub.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The user would prefer to get errors as early as possible. The plugin wouldn't have to be compiled into the apiserver or kubectl in order to enable that; we could use swagger.

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