diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 08859e30d8d..c38ef5dffb5 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -749,16 +749,25 @@ func Example_printPodHideTerminated() { } cmd := NewCmdRun(f, os.Stdin, os.Stdout, os.Stderr) podList := newAllPhasePodList() - mapper, _ := f.Object() - 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() + 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() { diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 0d866c4f164..514699b01e9 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -23,6 +23,7 @@ import ( "github.com/renstrom/dedent" "github.com/spf13/cobra" + "github.com/golang/glog" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -156,6 +157,8 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm showKind := cmdutil.GetFlagBool(cmd, "show-kind") mapper, typer := f.Object() printAll := false + filterFuncs := f.DefaultResourceFilterFunc() + filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces) cmdNamespace, enforceNamespace, err := f.DefaultNamespace() if err != nil { @@ -239,11 +242,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.AfterPrint(errOut, mapping.Resource) + filteredResourceCount++ + cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, errOut, filterOpts) } // print watched changes @@ -253,6 +258,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 @@ -261,7 +267,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.AfterPrint(errOut, mapping.Resource) + filteredResourceCount++ + cmdutil.PrintFilterCount(filteredResourceCount, mapping.Resource, errOut, filterOpts) } return err }) @@ -320,10 +327,36 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm return err } - if err := printer.PrintObj(obj, out); err != nil { - allErrs = append(allErrs, err) + isList := meta.IsListType(obj) + if isList { + filteredResourceCount, items, errs := cmdutil.FilterResourceList(obj, filterFuncs, filterOpts) + if errs != nil { + return errs + } + filteredObj, err := cmdutil.ObjectListToVersionedObject(items, version) + if err != nil { + return err + } + if err := printer.PrintObj(filteredObj, out); err != nil { + allErrs = append(allErrs, err) + } + + cmdutil.PrintFilterCount(filteredResourceCount, res, errOut, filterOpts) + return utilerrors.NewAggregate(allErrs) } - printer.AfterPrint(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 { + allErrs = append(allErrs, err) + } + } else if isFiltered { + filteredResourceCount++ + } + + cmdutil.PrintFilterCount(filteredResourceCount, res, errOut, filterOpts) return utilerrors.NewAggregate(allErrs) } @@ -372,8 +405,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, printAll) { + if cmdutil.MustPrintWithKinds(objs, infos, sorter, printAll) { showKind = true } @@ -390,7 +424,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.AfterPrint(errOut, lastMapping.Resource) + cmdutil.PrintFilterCount(filteredResourceCount, lastMapping.Resource, errOut, filterOpts) } printer, err = f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { @@ -399,6 +433,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 { @@ -429,37 +473,8 @@ func RunGet(f *cmdutil.Factory, out io.Writer, errOut io.Writer, cmd *cobra.Comm } } w.Flush() - if printer != nil { - printer.AfterPrint(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, printAll bool) bool { - var lastMap *meta.RESTMapping - - if len(infos) == 1 && printAll { - return true - } - - 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 -} diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index f8eac0a8012..1129eb1dd22 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -1179,6 +1179,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) diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index bd612f6bbb3..ec50d80be94 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -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" @@ -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" ) @@ -59,7 +60,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 { @@ -626,3 +627,78 @@ 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, printAll bool) bool { + var lastMap *meta.RESTMapping + + if len(infos) == 1 && printAll { + return true + } + + 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 +} diff --git a/pkg/kubectl/resource/result.go b/pkg/kubectl/resource/result.go index 892b30e947a..78f11e4a533 100644 --- a/pkg/kubectl/resource/result.go +++ b/pkg/kubectl/resource/result.go @@ -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 } @@ -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 } @@ -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() { diff --git a/pkg/kubectl/resource_filter.go b/pkg/kubectl/resource_filter.go new file mode 100644 index 00000000000..8ca31d83ac3 --- /dev/null +++ b/pkg/kubectl/resource_filter.go @@ -0,0 +1,67 @@ +/* +Copyright 2016 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package kubectl + +import ( + "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" + "k8s.io/kubernetes/pkg/runtime" +) + +// FilterFunc is a function that knows how to filter a specific resource kind. +// It receives a generic runtime.Object which must be type-checked by the function. +// Returns a boolean value true if a resource is filtered, or false otherwise. +type FilterFunc func(runtime.Object, PrintOptions) bool + +// Filters is a collection of filter funcs +type Filters []FilterFunc + +func NewResourceFilter() Filters { + return []FilterFunc{ + filterPods, + } +} + +// filterPods returns true if a pod should be skipped. +// defaults to true for terminated pods +func filterPods(obj runtime.Object, options PrintOptions) bool { + switch p := obj.(type) { + case *v1.Pod: + reason := string(p.Status.Phase) + if p.Status.Reason != "" { + reason = p.Status.Reason + } + return !options.ShowAll && (reason == string(v1.PodSucceeded) || reason == string(v1.PodFailed)) + case *api.Pod: + reason := string(p.Status.Phase) + if p.Status.Reason != "" { + reason = p.Status.Reason + } + return !options.ShowAll && (reason == string(api.PodSucceeded) || reason == string(api.PodFailed)) + } + return false +} + +// Filter loops through a collection of FilterFuncs until it finds one that can filter the given resource +func (f Filters) Filter(obj runtime.Object, opts *PrintOptions) (bool, error) { + for _, filter := range f { + if ok := filter(obj, *opts); ok { + return true, nil + } + } + return false, nil +} diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index a17b4ec7bc1..5716ac6181a 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -448,10 +448,6 @@ func (h *HumanReadablePrinter) HandledResources() []string { } func (h *HumanReadablePrinter) AfterPrint(output io.Writer, res string) error { - if !h.options.NoHeaders && !h.options.ShowAll && h.hiddenObjNum > 0 { - _, err := fmt.Fprintf(output, "\ninfo: %d completed %s were not shown in the list. Pass --show-all to see all.\n\n", h.hiddenObjNum, res) - return err - } return nil } @@ -497,12 +493,6 @@ var networkPolicyColumns = []string{"NAME", "POD-SELECTOR", "AGE"} var certificateSigningRequestColumns = []string{"NAME", "AGE", "REQUESTOR", "CONDITION"} func (h *HumanReadablePrinter) printPod(pod *api.Pod, w io.Writer, options PrintOptions) error { - reason := string(pod.Status.Phase) - // if not printing all pods, skip terminated pods (default) - if !options.ShowAll && (reason == string(api.PodSucceeded) || reason == string(api.PodFailed)) { - h.hiddenObjNum++ - return nil - } if err := printPodBase(pod, w, options); err != nil { return err } @@ -512,13 +502,6 @@ func (h *HumanReadablePrinter) printPod(pod *api.Pod, w io.Writer, options Print func (h *HumanReadablePrinter) printPodList(podList *api.PodList, w io.Writer, options PrintOptions) error { for _, pod := range podList.Items { - reason := string(pod.Status.Phase) - // if not printing all pods, skip terminated pods (default) - if !options.ShowAll && (reason == string(api.PodSucceeded) || reason == string(api.PodFailed)) { - h.hiddenObjNum++ - continue - } - if err := printPodBase(&pod, w, options); err != nil { return err } diff --git a/pkg/kubectl/resource_printer_test.go b/pkg/kubectl/resource_printer_test.go index 2f341acfaf9..cc04248570f 100644 --- a/pkg/kubectl/resource_printer_test.go +++ b/pkg/kubectl/resource_printer_test.go @@ -1148,7 +1148,7 @@ func TestPrintPod(t *testing.T) { } buf := bytes.NewBuffer([]byte{}) - printer := HumanReadablePrinter{hiddenObjNum: 0} + printer := HumanReadablePrinter{} for _, test := range tests { printer.printPod(&test.pod, buf, PrintOptions{false, false, false, false, true, false, false, "", []string{}}) // We ignore time @@ -1157,9 +1157,6 @@ func TestPrintPod(t *testing.T) { } buf.Reset() } - if printer.hiddenObjNum > 0 { - t.Fatalf("Expected hidden pods: 0, got: %d", printer.hiddenObjNum) - } } func TestPrintNonTerminatedPod(t *testing.T) { @@ -1245,7 +1242,7 @@ func TestPrintNonTerminatedPod(t *testing.T) { } buf := bytes.NewBuffer([]byte{}) - printer := HumanReadablePrinter{hiddenObjNum: 0} + printer := HumanReadablePrinter{} for _, test := range tests { printer.printPod(&test.pod, buf, PrintOptions{false, false, false, false, false, false, false, "", []string{}}) // We ignore time @@ -1254,9 +1251,6 @@ func TestPrintNonTerminatedPod(t *testing.T) { } buf.Reset() } - if printer.hiddenObjNum != 2 { - t.Fatalf("Expected hidden pods: 2, got: %d", printer.hiddenObjNum) - } } func TestPrintPodWithLabels(t *testing.T) { @@ -1309,7 +1303,7 @@ func TestPrintPodWithLabels(t *testing.T) { } buf := bytes.NewBuffer([]byte{}) - printer := HumanReadablePrinter{hiddenObjNum: 0} + printer := HumanReadablePrinter{} for _, test := range tests { printer.printPod(&test.pod, buf, PrintOptions{false, false, false, false, false, false, false, "", test.labelColumns}) // We ignore time @@ -1318,9 +1312,6 @@ func TestPrintPodWithLabels(t *testing.T) { } buf.Reset() } - if printer.hiddenObjNum > 0 { - t.Fatalf("Expected hidden pods: 0, got: %d", printer.hiddenObjNum) - } } type stringTestList []struct { @@ -1519,7 +1510,7 @@ func TestPrintPodShowLabels(t *testing.T) { } buf := bytes.NewBuffer([]byte{}) - printer := HumanReadablePrinter{hiddenObjNum: 0} + printer := HumanReadablePrinter{} for _, test := range tests { printer.printPod(&test.pod, buf, PrintOptions{false, false, false, false, false, test.showLabels, false, "", []string{}}) @@ -1529,7 +1520,4 @@ func TestPrintPodShowLabels(t *testing.T) { } buf.Reset() } - if printer.hiddenObjNum > 0 { - t.Fatalf("Expected hidden pods: 0, got: %d", printer.hiddenObjNum) - } }