From 293507538879f6f2bbe8e5047ee925ce8d6a2b5f Mon Sep 17 00:00:00 2001 From: Brendan Burns Date: Mon, 19 Oct 2015 22:44:48 -0700 Subject: [PATCH] Fix sorting from 2 bugs --- pkg/kubectl/cmd/get.go | 45 +++++++++++++++---- pkg/kubectl/sorting_printer.go | 69 ++++++++++++++++++++++++----- pkg/kubectl/sorting_printer_test.go | 62 ++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 21 deletions(-) diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 0396423a26b..b41c159fdb8 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -25,6 +25,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/watch" ) @@ -220,25 +221,51 @@ func RunGet(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string return printer.PrintObj(obj, out) } + infos, err := b.Flatten().Do().Infos() + if err != nil { + return err + } + objs := make([]runtime.Object, len(infos)) + for ix := range infos { + objs[ix] = infos[ix].Object + } + + sorting, err := cmd.Flags().GetString("sort-by") + var sorter *kubectl.RuntimeSort + if err == nil && len(sorting) > 0 { + if sorter, err = kubectl.SortObjects(objs, sorting); err != nil { + return err + } + } + // use the default printer for each object printer = nil var lastMapping *meta.RESTMapping w := kubectl.GetNewTabWriter(out) defer w.Flush() - return b.Flatten().Do().Visit(func(r *resource.Info, err error) error { - if err != nil { - return err + + for ix := range objs { + var mapping *meta.RESTMapping + if sorter != nil { + mapping = infos[sorter.OriginalPosition(ix)].Mapping + } else { + mapping = infos[ix].Mapping } - if printer == nil || lastMapping == nil || r.Mapping == nil || r.Mapping.Resource != lastMapping.Resource { - printer, err = f.PrinterForMapping(cmd, r.Mapping, allNamespaces) + if printer == nil || lastMapping == nil || mapping == nil || mapping.Resource != lastMapping.Resource { + printer, err = f.PrinterForMapping(cmd, mapping, allNamespaces) if err != nil { return err } - lastMapping = r.Mapping + lastMapping = mapping } if _, found := printer.(*kubectl.HumanReadablePrinter); found { - return printer.PrintObj(r.Object, w) + if err := printer.PrintObj(objs[ix], w); err != nil { + return err + } } - return printer.PrintObj(r.Object, out) - }) + if err := printer.PrintObj(objs[ix], out); err != nil { + return err + } + } + return nil } diff --git a/pkg/kubectl/sorting_printer.go b/pkg/kubectl/sorting_printer.go index 2ec610d4284..08be4b024ef 100644 --- a/pkg/kubectl/sorting_printer.go +++ b/pkg/kubectl/sorting_printer.go @@ -23,6 +23,7 @@ import ( "sort" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/v1" "k8s.io/kubernetes/pkg/runtime" "k8s.io/kubernetes/pkg/util/jsonpath" @@ -60,8 +61,35 @@ func (s *SortingPrinter) sortObj(obj runtime.Object) error { if len(objs) == 0 { return nil } + + sorter, err := SortObjects(objs, s.SortField) + if err != nil { + return err + } + + switch list := obj.(type) { + case *v1.List: + outputList := make([]runtime.RawExtension, len(objs)) + for ix := range objs { + outputList[ix] = list.Items[sorter.OriginalPosition(ix)] + } + list.Items = outputList + return nil + } + return runtime.SetList(obj, objs) +} + +func SortObjects(objs []runtime.Object, fieldInput string) (*RuntimeSort, error) { parser := jsonpath.New("sorting") - parser.Parse(s.SortField) + + field, err := massageJSONPath(fieldInput) + if err != nil { + return nil, err + } + + if err := parser.Parse(field); err != nil { + return nil, err + } for ix := range objs { item := objs[ix] @@ -69,31 +97,38 @@ func (s *SortingPrinter) sortObj(obj runtime.Object) error { case *runtime.Unknown: var err error if objs[ix], err = api.Codec.Decode(u.RawJSON); err != nil { - return err + return nil, err } } } + values, err := parser.FindResults(reflect.ValueOf(objs[0]).Elem().Interface()) if err != nil { - return err + return nil, err } if len(values) == 0 { - return fmt.Errorf("couldn't find any field with path: %s", s.SortField) - } - sorter := &RuntimeSort{ - field: s.SortField, - objs: objs, + return nil, fmt.Errorf("couldn't find any field with path: %s", field) } + + sorter := NewRuntimeSort(field, objs) sort.Sort(sorter) - runtime.SetList(obj, sorter.objs) - return nil + return sorter, nil } // RuntimeSort is an implementation of the golang sort interface that knows how to sort // lists of runtime.Object type RuntimeSort struct { - field string - objs []runtime.Object + field string + objs []runtime.Object + origPosition []int +} + +func NewRuntimeSort(field string, objs []runtime.Object) *RuntimeSort { + sorter := &RuntimeSort{field: field, objs: objs, origPosition: make([]int, len(objs))} + for ix := range objs { + sorter.origPosition[ix] = ix + } + return sorter } func (r *RuntimeSort) Len() int { @@ -102,6 +137,7 @@ func (r *RuntimeSort) Len() int { func (r *RuntimeSort) Swap(i, j int) { r.objs[i], r.objs[j] = r.objs[j], r.objs[i] + r.origPosition[i], r.origPosition[j] = r.origPosition[j], r.origPosition[i] } func isLess(i, j reflect.Value) (bool, error) { @@ -146,3 +182,12 @@ func (r *RuntimeSort) Less(i, j int) bool { } return less } + +// Returns the starting (original) position of a particular index. e.g. If OriginalPosition(0) returns 5 than the +// the item currently at position 0 was at position 5 in the original unsorted array. +func (r *RuntimeSort) OriginalPosition(ix int) int { + if ix < 0 || ix > len(r.origPosition) { + return -1 + } + return r.origPosition[ix] +} diff --git a/pkg/kubectl/sorting_printer_test.go b/pkg/kubectl/sorting_printer_test.go index 9abb1e701d6..e7d2510e67c 100644 --- a/pkg/kubectl/sorting_printer_test.go +++ b/pkg/kubectl/sorting_printer_test.go @@ -24,9 +24,35 @@ import ( "k8s.io/kubernetes/pkg/runtime" ) +func encodeOrDie(obj runtime.Object) []byte { + data, err := api.Codec.Encode(obj) + if err != nil { + panic(err.Error()) + } + return data +} + func TestSortingPrinter(t *testing.T) { intPtr := func(val int) *int { return &val } + a := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "a", + }, + } + + b := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "b", + }, + } + + c := &api.Pod{ + ObjectMeta: api.ObjectMeta{ + Name: "c", + }, + } + tests := []struct { obj runtime.Object sort runtime.Object @@ -159,6 +185,42 @@ func TestSortingPrinter(t *testing.T) { }, field: "{.spec.replicas}", }, + { + name: "v1.List in order", + obj: &api.List{ + Items: []runtime.RawExtension{ + {RawJSON: encodeOrDie(a)}, + {RawJSON: encodeOrDie(b)}, + {RawJSON: encodeOrDie(c)}, + }, + }, + sort: &api.List{ + Items: []runtime.RawExtension{ + {RawJSON: encodeOrDie(a)}, + {RawJSON: encodeOrDie(b)}, + {RawJSON: encodeOrDie(c)}, + }, + }, + field: "{.metadata.name}", + }, + { + name: "v1.List in reverse", + obj: &api.List{ + Items: []runtime.RawExtension{ + {RawJSON: encodeOrDie(c)}, + {RawJSON: encodeOrDie(b)}, + {RawJSON: encodeOrDie(a)}, + }, + }, + sort: &api.List{ + Items: []runtime.RawExtension{ + {RawJSON: encodeOrDie(a)}, + {RawJSON: encodeOrDie(b)}, + {RawJSON: encodeOrDie(c)}, + }, + }, + field: "{.metadata.name}", + }, } for _, test := range tests { sort := &SortingPrinter{SortField: test.field}