From 9e180fe334e5c575b881192cd285df628da19a2a Mon Sep 17 00:00:00 2001 From: Mike Danese Date: Tue, 13 Sep 2016 13:10:21 -0700 Subject: [PATCH] allow kubectl -f to filter by selector --- pkg/kubectl/cmd/apply.go | 3 ++ pkg/kubectl/resource/builder.go | 75 +++++++++++++++------------------ pkg/kubectl/resource/visitor.go | 49 +++++++++++++++++++++ 3 files changed, 87 insertions(+), 40 deletions(-) diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 45a46f14e3e11..c95980f1fb6fa 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -40,6 +40,7 @@ import ( type ApplyOptions struct { Filenames []string Recursive bool + Selector string } const ( @@ -86,6 +87,7 @@ func NewCmdApply(f *cmdutil.Factory, out io.Writer) *cobra.Command { kubectl.AddJsonFilenameFlag(cmd, &options.Filenames, usage) cmd.MarkFlagRequired("filename") cmd.Flags().Bool("overwrite", true, "Automatically resolve conflicts between the modified and live configuration by using values from the modified configuration") + cmd.Flags().StringVarP(&options.Selector, "selector", "l", "", "Selector (label query) to filter on") cmdutil.AddValidateFlags(cmd) cmdutil.AddRecursiveFlag(cmd, &options.Recursive) cmdutil.AddOutputFlagsForMutation(cmd) @@ -120,6 +122,7 @@ func RunApply(f *cmdutil.Factory, cmd *cobra.Command, out io.Writer, options *Ap ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace(). FilenameParam(enforceNamespace, options.Recursive, options.Filenames...). + SelectorParam(options.Selector). Flatten(). Do() err = r.Err() diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 41fc1a8b515a0..01f2ee031c1cc 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -531,6 +531,41 @@ func (b *Builder) visitorResult() *Result { b.selector = labels.Everything() } + // visit items specified by paths + if len(b.paths) != 0 { + singular := !b.dir && !b.stream && len(b.paths) == 1 + if len(b.resources) != 0 { + return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")} + } + if len(b.resourceTuples) != 0 { + return &Result{err: fmt.Errorf("paths cannot be used when passing resource/name arguments")} + } + + var visitors Visitor + if b.continueOnError { + visitors = EagerVisitorList(b.paths) + } else { + visitors = VisitorList(b.paths) + } + + // only items from disk can be refetched + if b.latest { + // must flatten lists prior to fetching + if b.flatten { + visitors = NewFlattenListVisitor(visitors, b.mapper) + } + // must set namespace prior to fetching + if b.defaultNamespace { + visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace)) + } + visitors = NewDecoratedVisitor(visitors, RetrieveLatest) + } + if b.selector != nil { + visitors = NewFilteredVisitor(visitors, FilterBySelector(b.selector)) + } + return &Result{singular: singular, visitor: visitors, sources: b.paths} + } + // visit selectors if b.selector != nil { if len(b.names) != 0 { @@ -542,14 +577,6 @@ func (b *Builder) visitorResult() *Result { if len(b.resources) == 0 { return &Result{err: fmt.Errorf("at least one resource must be specified to use a selector")} } - // empty selector has different error message for paths being provided - if len(b.paths) != 0 { - if b.selector.Empty() { - return &Result{err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")} - } else { - return &Result{err: fmt.Errorf("a selector may not be specified when path, URL, or stdin is provided as input")} - } - } mappings, err := b.resourceMappings() if err != nil { return &Result{err: err} @@ -582,9 +609,6 @@ func (b *Builder) visitorResult() *Result { isSingular = len(b.resourceTuples) == 1 } - if len(b.paths) != 0 { - return &Result{singular: isSingular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify a resource by arguments as well")} - } if len(b.resources) != 0 { return &Result{singular: isSingular, err: fmt.Errorf("you may not specify individual resources and bulk resources in the same call")} } @@ -683,35 +707,6 @@ func (b *Builder) visitorResult() *Result { return &Result{singular: isSingular, visitor: VisitorList(visitors), sources: visitors} } - // visit items specified by paths - if len(b.paths) != 0 { - singular := !b.dir && !b.stream && len(b.paths) == 1 - if len(b.resources) != 0 { - return &Result{singular: singular, err: fmt.Errorf("when paths, URLs, or stdin is provided as input, you may not specify resource arguments as well")} - } - - var visitors Visitor - if b.continueOnError { - visitors = EagerVisitorList(b.paths) - } else { - visitors = VisitorList(b.paths) - } - - // only items from disk can be refetched - if b.latest { - // must flatten lists prior to fetching - if b.flatten { - visitors = NewFlattenListVisitor(visitors, b.mapper) - } - // must set namespace prior to fetching - if b.defaultNamespace { - visitors = NewDecoratedVisitor(visitors, SetNamespace(b.namespace)) - } - visitors = NewDecoratedVisitor(visitors, RetrieveLatest) - } - return &Result{singular: singular, visitor: visitors, sources: b.paths} - } - if len(b.resources) != 0 { return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified")} } diff --git a/pkg/kubectl/resource/visitor.go b/pkg/kubectl/resource/visitor.go index 365427c4ea1e0..1698080de6c7a 100644 --- a/pkg/kubectl/resource/visitor.go +++ b/pkg/kubectl/resource/visitor.go @@ -29,6 +29,7 @@ import ( "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" + "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/runtime" utilerrors "k8s.io/kubernetes/pkg/util/errors" "k8s.io/kubernetes/pkg/util/yaml" @@ -631,3 +632,51 @@ func RetrieveLazy(info *Info, err error) error { } return nil } + +type FilterFunc func(info *Info, err error) (bool, error) + +type FilteredVisitor struct { + visitor Visitor + filters []FilterFunc +} + +func NewFilteredVisitor(v Visitor, fn ...FilterFunc) Visitor { + if len(fn) == 0 { + return v + } + return FilteredVisitor{v, fn} +} + +func (v FilteredVisitor) Visit(fn VisitorFunc) error { + return v.visitor.Visit(func(info *Info, err error) error { + if err != nil { + return err + } + for _, filter := range v.filters { + ok, err := filter(info, nil) + if err != nil { + return err + } + if !ok { + return nil + } + } + return fn(info, nil) + }) +} + +func FilterBySelector(s labels.Selector) FilterFunc { + return func(info *Info, err error) (bool, error) { + if err != nil { + return false, err + } + a, err := meta.Accessor(info.Object) + if err != nil { + return false, err + } + if !s.Matches(labels.Set(a.GetLabels())) { + return false, nil + } + return true, nil + } +}