Skip to content

Commit

Permalink
UPSTREAM: 31163: add resource filter handling
Browse files Browse the repository at this point in the history
UPSTREAM: kubernetes#31163
Fixes openshift/origin#9901

Resources are currently filtered (in order to prevent printing) at print
time in their HumanReadablePrinter handlers. This design makes it not
possible to filter objects when they are printed using any other
printer, such as YAML, JSON, or the NamePrinter.

This patch removes any filters previously added at the printer level for
pods and adds a way to define resource-specific filters before they are
sent to a printer handler. A woking filter handler for pods has also
been
implemented.

Filters affect resources being printed through the HumanReadablePrinter,
YAML, JSON, and `--template` printers.

:100644 100644 cf7d1af... 185d31b... M	pkg/kubectl/cmd/cmd_test.go
:100644 100644 6551226... 99d7838... M	pkg/kubectl/cmd/get.go
:100644 100644 49ffb4d... 72d6c0f... M	pkg/kubectl/cmd/util/factory.go
:100644 100644 adc82b6... e42e819... M	pkg/kubectl/cmd/util/helpers.go
:100644 100644 892b30e... 78f11e4... M	pkg/kubectl/resource/result.go
:000000 100644 0000000... 8ca31d8... A	pkg/kubectl/resource_filter.go
:100644 100644 5af291e... 0b07610... M	pkg/kubectl/resource_printer.go
:100644 100644 2f341ac... cc04248... M	pkg/kubectl/resource_printer_test.go
  • Loading branch information
juanvallejo authored and smarterclayton committed Sep 26, 2016
1 parent 0bfacd3 commit 7eb0b9d
Show file tree
Hide file tree
Showing 8 changed files with 244 additions and 83 deletions.
22 changes: 16 additions & 6 deletions pkg/kubectl/cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -723,16 +723,26 @@ func Example_printPodHideTerminated() {
}
cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr)
podList := newAllPhasePodList()
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, podList, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)

// filter pods
filterFuncs := f.DefaultResourceFilterFunc()
filterOpts := f.DefaultResourceFilterOptions(cmd, false)
_, filteredPodList, errs := cmdutil.FilterResourceList(podList, filterFuncs, filterOpts)
if errs != nil {
fmt.Printf("Unexpected filter error: %v\n", errs)
}
for _, pod := range filteredPodList {
mapper, _ := f.Object(false)
err := f.PrintObject(cmd, mapper, pod, os.Stdout)
if err != nil {
fmt.Printf("Unexpected error: %v", err)
}
}
// Output:
// NAME READY STATUS RESTARTS AGE
// test1 1/2 Pending 6 10y
// test2 1/2 Running 6 10y
// test5 1/2 Unknown 6 10y
// test2 1/2 Running 6 10y
// test5 1/2 Unknown 6 10y
}

func Example_printPodShowAll() {
Expand Down
87 changes: 53 additions & 34 deletions pkg/kubectl/cmd/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"io"

"github.com/golang/glog"
"github.com/renstrom/dedent"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api/meta"
Expand Down Expand Up @@ -156,6 +157,8 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces")
showKind := cmdutil.GetFlagBool(cmd, "show-kind")
mapper, typer := f.Object(cmdutil.GetIncludeThirdPartyAPIs(cmd))
filterFuncs := f.DefaultResourceFilterFunc()
filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces)

cmdNamespace, enforceNamespace, err := f.DefaultNamespace()
if err != nil {
Expand Down Expand Up @@ -231,11 +234,13 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
}

// print the current object
filteredResourceCount := 0
if !isWatchOnly {
if err := printer.PrintObj(obj, out); err != nil {
return fmt.Errorf("unable to output the provided object: %v", err)
}
printer.FinishPrint(errOut, mapping.Resource)
filteredResourceCount++
cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, errOut, filterOpts)
}

// print watched changes
Expand All @@ -245,6 +250,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
}

first := true
filteredResourceCount = 0
kubectl.WatchLoop(w, func(e watch.Event) error {
if !isList && first {
// drop the initial watch event in the single resource case
Expand All @@ -253,7 +259,8 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
}
err := printer.PrintObj(e.Object, out)
if err == nil {
printer.FinishPrint(errOut, mapping.Resource)
filteredResourceCount++
cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, errOut, filterOpts)
}
return err
})
Expand Down Expand Up @@ -313,11 +320,37 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
return err
}

if err := printer.PrintObj(obj, out); err != nil {
errs = append(errs, err)
isList := meta.IsListType(obj)
if isList {
filteredResourceCount, items, err := cmdutil.FilterResourceList(obj, filterFuncs, filterOpts)
if err != nil {
return err
}
filteredObj, err := cmdutil.ObjectListToVersionedObject(items, version)
if err != nil {
return err
}
if err := printer.PrintObj(filteredObj, out); err != nil {
errs = append(errs, err)
}

cmdutil.PrintFilterCount(filteredResourceCount, res, errOut, filterOpts)
// we can receive multiple layers of aggregates, so attempt to flatten them as much
// as is possible before returning.
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
}
printer.FinishPrint(errOut, res)

filteredResourceCount := 0
if isFiltered, err := filterFuncs.Filter(obj, filterOpts); !isFiltered {
if err != nil {
glog.V(2).Infof("Unable to filter resource: %v", err)
} else if err := printer.PrintObj(obj, out); err != nil {
errs = append(errs, err)
}
} else if isFiltered {
filteredResourceCount++
}
cmdutil.PrintFilterCount(filteredResourceCount, res, errOut, filterOpts)
// we can receive multiple layers of aggregates, so attempt to flatten them as much
// as is possible before returning.
return utilerrors.Reduce(utilerrors.Flatten(utilerrors.NewAggregate(errs)))
Expand Down Expand Up @@ -368,8 +401,9 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
printer = nil
var lastMapping *meta.RESTMapping
w := kubectl.GetNewTabWriter(out)
filteredResourceCount := 0

if mustPrintWithKinds(objs, infos, sorter) {
if cmdutil.MustPrintWithKinds(objs, infos, sorter) {
showKind = true
}

Expand All @@ -386,7 +420,7 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
if printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource {
if printer != nil {
w.Flush()
printer.FinishPrint(errOut, lastMapping.Resource)
cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, errOut, filterOpts)
}
printer, err = f.PrinterForMapping(cmd, mapping, allNamespaces)
if err != nil {
Expand All @@ -395,6 +429,16 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
}
lastMapping = mapping
}

// filter objects if filter has been defined for current object
if isFiltered, err := filterFuncs.Filter(original, filterOpts); isFiltered {
if err == nil {
filteredResourceCount++
continue
}
allErrs = append(allErrs, err)
}

if resourcePrinter, found := printer.(*kubectl.HumanReadablePrinter); found {
resourceName := resourcePrinter.GetResourceKind()
if mapping != nil {
Expand Down Expand Up @@ -425,33 +469,8 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm
}
}
w.Flush()
if printer != nil {
printer.FinishPrint(errOut, lastMapping.Resource)
if printer != nil && lastMapping != nil {
cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, errOut, filterOpts)
}
return utilerrors.NewAggregate(allErrs)
}

// mustPrintWithKinds determines if printer is dealing
// with multiple resource kinds, in which case it will
// return true, indicating resource kind will be
// included as part of printer output
func mustPrintWithKinds(objs []runtime.Object, infos []*resource.Info, sorter *kubectl.RuntimeSort) bool {
var lastMap *meta.RESTMapping

for ix := range objs {
var mapping *meta.RESTMapping
if sorter != nil {
mapping = infos[sorter.OriginalPosition(ix)].Mapping
} else {
mapping = infos[ix].Mapping
}

// display "kind" only if we have mixed resources
if lastMap != nil && mapping.Resource != lastMap.Resource {
return true
}
lastMap = mapping
}

return false
}
23 changes: 23 additions & 0 deletions pkg/kubectl/cmd/util/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -1247,6 +1247,29 @@ func DefaultClientConfig(flags *pflag.FlagSet) clientcmd.ClientConfig {
return clientConfig
}

func (f *Factory) DefaultResourceFilterOptions(cmd *cobra.Command, withNamespace bool) *kubectl.PrintOptions {
columnLabel, err := cmd.Flags().GetStringSlice("label-columns")
if err != nil {
columnLabel = []string{}
}
opts := &kubectl.PrintOptions{
NoHeaders: GetFlagBool(cmd, "no-headers"),
WithNamespace: withNamespace,
Wide: GetWideFlag(cmd),
ShowAll: GetFlagBool(cmd, "show-all"),
ShowLabels: GetFlagBool(cmd, "show-labels"),
AbsoluteTimestamps: isWatch(cmd),
ColumnLabels: columnLabel,
}

return opts
}

// DefaultResourceFilterFunc returns a collection of FilterFuncs suitable for filtering specific resource types.
func (f *Factory) DefaultResourceFilterFunc() kubectl.Filters {
return kubectl.NewResourceFilter()
}

// PrintObject prints an api object given command line flags to modify the output format
func (f *Factory) PrintObject(cmd *cobra.Command, mapper meta.RESTMapper, obj runtime.Object, out io.Writer) error {
gvks, _, err := api.Scheme.ObjectKinds(obj)
Expand Down
76 changes: 74 additions & 2 deletions pkg/kubectl/cmd/util/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"strings"
"time"

"k8s.io/kubernetes/pkg/api"
kerrors "k8s.io/kubernetes/pkg/api/errors"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned"
Expand All @@ -43,7 +44,7 @@ import (
"k8s.io/kubernetes/pkg/util/sets"
"k8s.io/kubernetes/pkg/util/strategicpatch"

"github.com/evanphx/json-patch"
jsonpatch "github.com/evanphx/json-patch"
"github.com/golang/glog"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
Expand All @@ -60,7 +61,7 @@ type debugError interface {

// AddSourceToErr adds handleResourcePrefix and source string to error message.
// verb is the string like "creating", "deleting" etc.
// souce is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
// source is the filename or URL to the template file(*.json or *.yaml), or stdin to use to handle the resource.
func AddSourceToErr(verb string, source string, err error) error {
if source != "" {
if statusError, ok := err.(kerrors.APIStatus); ok {
Expand Down Expand Up @@ -636,3 +637,74 @@ func MaybeConvertObject(obj runtime.Object, gv unversioned.GroupVersion, convert
return converter.ConvertToVersion(obj, gv)
}
}

// MustPrintWithKinds determines if printer is dealing
// with multiple resource kinds, in which case it will
// return true, indicating resource kind will be
// included as part of printer output
func MustPrintWithKinds(objs []runtime.Object, infos []*resource.Info, sorter *kubectl.RuntimeSort) bool {
var lastMap *meta.RESTMapping

for ix := range objs {
var mapping *meta.RESTMapping
if sorter != nil {
mapping = infos[sorter.OriginalPosition(ix)].Mapping
} else {
mapping = infos[ix].Mapping
}

// display "kind" only if we have mixed resources
if lastMap != nil && mapping.Resource != lastMap.Resource {
return true
}
lastMap = mapping
}

return false
}

// FilterResourceList receives a list of runtime objects.
// If any objects are filtered, that number is returned along with a modified list.
func FilterResourceList(obj runtime.Object, filterFuncs kubectl.Filters, filterOpts *kubectl.PrintOptions) (int, []runtime.Object, error) {
items, err := meta.ExtractList(obj)
if err != nil {
return 0, []runtime.Object{obj}, utilerrors.NewAggregate([]error{err})
}
if errs := runtime.DecodeList(items, api.Codecs.UniversalDecoder(), runtime.UnstructuredJSONScheme); len(errs) > 0 {
return 0, []runtime.Object{obj}, utilerrors.NewAggregate(errs)
}

filterCount := 0
list := make([]runtime.Object, 0, len(items))
for _, obj := range items {
if isFiltered, err := filterFuncs.Filter(obj, filterOpts); !isFiltered {
if err != nil {
glog.V(2).Infof("Unable to filter resource: %v", err)
continue
}
list = append(list, obj)
} else if isFiltered {
filterCount++
}
}
return filterCount, list, nil
}

func PrintFilterCount(hiddenObjNum int, resource string, out io.Writer, options *kubectl.PrintOptions) error {
if !options.NoHeaders && !options.ShowAll && hiddenObjNum > 0 {
_, err := fmt.Fprintf(out, " info: %d completed object(s) was(were) not shown in %s list. Pass --show-all to see all objects.\n\n", hiddenObjNum, resource)
return err
}
return nil
}

// ObjectListToVersionedObject receives a list of api objects and a group version
// and squashes the list's items into a single versioned runtime.Object.
func ObjectListToVersionedObject(objects []runtime.Object, version unversioned.GroupVersion) (runtime.Object, error) {
objectList := &api.List{Items: objects}
converted, err := resource.TryConvert(api.Scheme, objectList, version, registered.GroupOrDie(api.GroupName).GroupVersion)
if err != nil {
return nil, err
}
return converted, nil
}
8 changes: 4 additions & 4 deletions pkg/kubectl/resource/result.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ func AsVersionedObject(infos []*Info, forceList bool, version unversioned.GroupV
object = objects[0]
} else {
object = &api.List{Items: objects}
converted, err := tryConvert(api.Scheme, object, version, registered.GroupOrDie(api.GroupName).GroupVersion)
converted, err := TryConvert(api.Scheme, object, version, registered.GroupOrDie(api.GroupName).GroupVersion)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -263,7 +263,7 @@ func AsVersionedObjects(infos []*Info, version unversioned.GroupVersion, encoder
}
}

converted, err := tryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion())
converted, err := TryConvert(info.Mapping.ObjectConvertor, info.Object, version, info.Mapping.GroupVersionKind.GroupVersion())
if err != nil {
return nil, err
}
Expand All @@ -272,9 +272,9 @@ func AsVersionedObjects(infos []*Info, version unversioned.GroupVersion, encoder
return objects, nil
}

// tryConvert attempts to convert the given object to the provided versions in order. This function assumes
// TryConvert attempts to convert the given object to the provided versions in order. This function assumes
// the object is in internal version.
func tryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...unversioned.GroupVersion) (runtime.Object, error) {
func TryConvert(converter runtime.ObjectConvertor, object runtime.Object, versions ...unversioned.GroupVersion) (runtime.Object, error) {
var last error
for _, version := range versions {
if version.Empty() {
Expand Down
Loading

0 comments on commit 7eb0b9d

Please sign in to comment.