diff --git a/pkg/kubectl/cmd/apply.go b/pkg/kubectl/cmd/apply.go index 8f4a0792612..df3b2190079 100644 --- a/pkg/kubectl/cmd/apply.go +++ b/pkg/kubectl/cmd/apply.go @@ -367,8 +367,8 @@ func RunApply(f cmdutil.Factory, cmd *cobra.Command, out, errOut io.Writer, opti clientFunc: f.UnstructuredClientForMapping, clientsetFunc: f.ClientSet, - selector: options.Selector, - visitedUids: visitedUids, + labelSelector: options.Selector, + visitedUids: visitedUids, cascade: options.Cascade, dryRun: dryRun, @@ -453,8 +453,9 @@ type pruner struct { clientFunc resource.ClientMapperFunc clientsetFunc func() (internalclientset.Interface, error) - visitedUids sets.String - selector string + visitedUids sets.String + labelSelector string + fieldSelector string cascade bool dryRun bool @@ -474,7 +475,8 @@ func (p *pruner) prune(namespace string, mapping *meta.RESTMapping, shortOutput, mapping.GroupVersionKind.Version, false, &metav1.ListOptions{ - LabelSelector: p.selector, + LabelSelector: p.labelSelector, + FieldSelector: p.fieldSelector, IncludeUninitialized: includeUninitialized, }, ) diff --git a/pkg/kubectl/cmd/resource/get.go b/pkg/kubectl/cmd/resource/get.go index 91bd346ff50..96931118393 100644 --- a/pkg/kubectl/cmd/resource/get.go +++ b/pkg/kubectl/cmd/resource/get.go @@ -56,6 +56,7 @@ type GetOptions struct { ChunkSize int64 LabelSelector string + FieldSelector string AllNamespaces bool Namespace string ExplicitNamespace bool @@ -158,6 +159,7 @@ func NewCmdGet(f cmdutil.Factory, out io.Writer, errOut io.Writer) *cobra.Comman cmd.Flags().Int64Var(&options.ChunkSize, "chunk-size", 500, "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.") cmd.Flags().BoolVar(&options.IgnoreNotFound, "ignore-not-found", options.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.") cmd.Flags().StringVarP(&options.LabelSelector, "selector", "l", options.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)") + cmd.Flags().StringVar(&options.FieldSelector, "field-selector", options.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.") cmd.Flags().BoolVar(&options.AllNamespaces, "all-namespaces", options.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.") cmdutil.AddIncludeUninitializedFlag(cmd) cmdutil.AddPrinterFlags(cmd) @@ -234,6 +236,7 @@ func (options *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []str NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces). FilenameParam(options.ExplicitNamespace, &options.FilenameOptions). LabelSelectorParam(options.LabelSelector). + FieldSelectorParam(options.FieldSelector). ExportParam(options.Export). RequestChunksOf(options.ChunkSize). IncludeUninitialized(cmdutil.ShouldIncludeUninitialized(cmd, false)). // TODO: this needs to be better factored @@ -451,6 +454,7 @@ func (options *GetOptions) watch(f cmdutil.Factory, cmd *cobra.Command, args []s NamespaceParam(options.Namespace).DefaultNamespace().AllNamespaces(options.AllNamespaces). FilenameParam(options.ExplicitNamespace, &options.FilenameOptions). LabelSelectorParam(options.LabelSelector). + FieldSelectorParam(options.FieldSelector). ExportParam(options.Export). RequestChunksOf(options.ChunkSize). IncludeUninitialized(includeUninitialized). diff --git a/pkg/kubectl/resource/builder.go b/pkg/kubectl/resource/builder.go index 6dbcdef187f..08b63ad7723 100644 --- a/pkg/kubectl/resource/builder.go +++ b/pkg/kubectl/resource/builder.go @@ -54,6 +54,7 @@ type Builder struct { dir bool labelSelector *string + fieldSelector *string selectAll bool includeUninitialized bool limitChunks int64 @@ -296,6 +297,22 @@ func (b *Builder) LabelSelector(selector string) *Builder { return b } +// FieldSelectorParam defines a selector that should be applied to the object types to load. +// This will not affect files loaded from disk or URL. If the parameter is empty it is +// a no-op - to select all resources. +func (b *Builder) FieldSelectorParam(s string) *Builder { + s = strings.TrimSpace(s) + if len(s) == 0 { + return b + } + if b.selectAll { + b.errs = append(b.errs, fmt.Errorf("found non-empty field selector %q with previously set 'all' parameter. ", s)) + return b + } + b.fieldSelector = &s + return b +} + // ExportParam accepts the export boolean for these resources func (b *Builder) ExportParam(export bool) *Builder { b.export = export @@ -350,7 +367,7 @@ func (b *Builder) RequestChunksOf(chunkSize int64) *Builder { // SelectEverythingParam func (b *Builder) SelectAllParam(selectAll bool) *Builder { - if selectAll && b.labelSelector != nil { + if selectAll && (b.labelSelector != nil || b.fieldSelector != nil) { b.errs = append(b.errs, fmt.Errorf("setting 'all' parameter but found a non empty selector. ")) return b } @@ -595,7 +612,7 @@ func (b *Builder) visitorResult() *Result { } // visit selectors - if b.labelSelector != nil { + if b.labelSelector != nil || b.fieldSelector != nil { return b.visitBySelector() } @@ -635,6 +652,14 @@ func (b *Builder) visitBySelector() *Result { return result } + var labelSelector, fieldSelector string + if b.labelSelector != nil { + labelSelector = *b.labelSelector + } + if b.fieldSelector != nil { + fieldSelector = *b.fieldSelector + } + visitors := []Visitor{} for _, mapping := range mappings { client, err := b.mapper.ClientForMapping(mapping) @@ -646,7 +671,7 @@ func (b *Builder) visitBySelector() *Result { if mapping.Scope.Name() != meta.RESTScopeNameNamespace { selectorNamespace = "" } - visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, *b.labelSelector, b.export, b.includeUninitialized, b.limitChunks)) + visitors = append(visitors, NewSelector(client, mapping, selectorNamespace, labelSelector, fieldSelector, b.export, b.includeUninitialized, b.limitChunks)) } if b.continueOnError { result.visitor = EagerVisitorList(visitors) diff --git a/pkg/kubectl/resource/helper.go b/pkg/kubectl/resource/helper.go index ddece216a5b..e5ca36221ed 100644 --- a/pkg/kubectl/resource/helper.go +++ b/pkg/kubectl/resource/helper.go @@ -75,15 +75,12 @@ func (m *Helper) List(namespace, apiVersion string, export bool, options *metav1 return req.Do().Get() } -func (m *Helper) Watch(namespace, resourceVersion, apiVersion string, labelSelector string) (watch.Interface, error) { +func (m *Helper) Watch(namespace, apiVersion string, options *metav1.ListOptions) (watch.Interface, error) { + options.Watch = true return m.RESTClient.Get(). NamespaceIfScoped(namespace, m.NamespaceScoped). Resource(m.Resource). - VersionedParams(&metav1.ListOptions{ - ResourceVersion: resourceVersion, - Watch: true, - LabelSelector: labelSelector, - }, metav1.ParameterCodec). + VersionedParams(options, metav1.ParameterCodec). Watch() } diff --git a/pkg/kubectl/resource/selector.go b/pkg/kubectl/resource/selector.go index 2ae4791bd38..c46d39ac8ac 100644 --- a/pkg/kubectl/resource/selector.go +++ b/pkg/kubectl/resource/selector.go @@ -31,18 +31,20 @@ type Selector struct { Mapping *meta.RESTMapping Namespace string LabelSelector string + FieldSelector string Export bool IncludeUninitialized bool LimitChunks int64 } // NewSelector creates a resource selector which hides details of getting items by their label selector. -func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace string, selector string, export, includeUninitialized bool, limitChunks int64) *Selector { +func NewSelector(client RESTClient, mapping *meta.RESTMapping, namespace, labelSelector, fieldSelector string, export, includeUninitialized bool, limitChunks int64) *Selector { return &Selector{ Client: client, Mapping: mapping, Namespace: namespace, - LabelSelector: selector, + LabelSelector: labelSelector, + FieldSelector: fieldSelector, Export: export, IncludeUninitialized: includeUninitialized, LimitChunks: limitChunks, @@ -59,6 +61,7 @@ func (r *Selector) Visit(fn VisitorFunc) error { r.Export, &metav1.ListOptions{ LabelSelector: r.LabelSelector, + FieldSelector: r.FieldSelector, IncludeUninitialized: r.IncludeUninitialized, Limit: r.LimitChunks, Continue: continueToken, @@ -71,17 +74,17 @@ func (r *Selector) Visit(fn VisitorFunc) error { if errors.IsBadRequest(err) || errors.IsNotFound(err) { if se, ok := err.(*errors.StatusError); ok { // modify the message without hiding this is an API error - if len(r.LabelSelector) == 0 { + if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 { se.ErrStatus.Message = fmt.Sprintf("Unable to list %q: %v", r.Mapping.Resource, se.ErrStatus.Message) } else { - se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.LabelSelector, se.ErrStatus.Message) + se.ErrStatus.Message = fmt.Sprintf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, se.ErrStatus.Message) } return se } - if len(r.LabelSelector) == 0 { + if len(r.LabelSelector) == 0 && len(r.FieldSelector) == 0 { return fmt.Errorf("Unable to list %q: %v", r.Mapping.Resource, err) } - return fmt.Errorf("Unable to find %q that match the selector %q: %v", r.Mapping.Resource, r.LabelSelector, err) + return fmt.Errorf("Unable to find %q that match label selector %q, field selector %q: %v", r.Mapping.Resource, r.LabelSelector, r.FieldSelector, err) } return err } @@ -107,7 +110,8 @@ func (r *Selector) Visit(fn VisitorFunc) error { } func (r *Selector) Watch(resourceVersion string) (watch.Interface, error) { - return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, resourceVersion, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), r.LabelSelector) + return NewHelper(r.Client, r.Mapping).Watch(r.Namespace, r.ResourceMapping().GroupVersionKind.GroupVersion().String(), + &metav1.ListOptions{ResourceVersion: resourceVersion, LabelSelector: r.LabelSelector, FieldSelector: r.FieldSelector}) } // ResourceMapping returns the mapping for this resource and implements ResourceMapping