Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks!--What happened: seems to have regressed in v1.13.0 and no longer sorts by creation timestamp. What you expected to happen: should sort by creation timestamp. How to reproduce it (as minimally and precisely as possible): Anything else we need to know?: N/A Environment: Kubernetes version (use ): <!-- DO NOT EDIT BELOW THIS LINE --/kind bug
/sig cli
/assign
bisected to () /assign
opened to fix
/assign

Discussion:
case *metav1beta1.Table: includesTable = true <del> if err := NewTableSorter(t, r.field).Sort(); err != nil { continue </del> <ins> if sorter, err := NewTableSorter(t, r.field); err != nil { return err } else if err := sorter.Sort(); err != nil { return err </ins> } default: includesRuntimeObjs = true

func (t *TableSorter) Swap(i, j int) { t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i] <ins> t.parsedRows[i], t.parsedRows[j] = t.parsedRows[j], t.parsedRows[i] </ins> } func (t *TableSorter) Less(i, j int) bool { iValues := t.parsedRows[i] jValues := t.parsedRows[j] <del> if len(iValues) == 0 || len(iValues[0]) == 0 || len(jValues) == 0 || len(jValues[0]) == 0 { klog.Fatalf("couldn't find any field with path %q in the list of objects", t.field) </del> <ins> if len(iValues) == 0 || len(iValues[0]) == 0 { return true } if len(jValues) == 0 || len(jValues[0]) == 0 { return false </ins> } iField := iValues[0][0]

return nil } <del> func NewTableSorter(table *metav1beta1.Table, field string) *TableSorter { </del> <ins> func NewTableSorter(table *metav1beta1.Table, field string) (*TableSorter, error) { </ins> var parsedRows [][][]reflect.Value parser := jsonpath.New("sorting").AllowMissingKeys(true) err := parser.Parse(field) if err != nil { <del> klog.Fatalf("sorting error: %vn", err) </del> <ins> return nil, fmt.Errorf("sorting error: %v", err) </ins> } <ins> fieldFoundOnce := false </ins> for i := range table.Rows { parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object) if err != nil { <del> klog.Fatalf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err) </del> <ins> return nil, fmt.Errorf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err) </ins> } parsedRows = append(parsedRows, parsedRow) <ins> if len(parsedRow) > 0 && len(parsedRow[0]) > 0 { fieldFoundOnce = true } } if len(table.Rows) > 0 && !fieldFoundOnce { return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field) </ins> } return &TableSorter{ obj:        table, field:      field, parsedRows: parsedRows, <del> } </del> <ins> }, nil </ins> } func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) { if unstructuredObj, ok := from.(*unstructured.Unstructured); ok {

package get import ( <ins> "encoding/json" </ins> "reflect" "strings" "testing"

"k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" <ins> metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" </ins> "k8s.io/apimachinery/pkg/runtime" <ins> "k8s.io/apimachinery/pkg/util/diff" </ins> "k8s.io/kubernetes/pkg/kubectl/scheme" ) <ins> func toUnstructuredOrDie(data []byte) *unstructured.Unstructured { unstrBody := map[string]interface{}{} err := json.Unmarshal(data, &unstrBody) if err != nil { panic(err) } return &unstructured.Unstructured{Object: unstrBody} } </ins> func encodeOrDie(obj runtime.Object) []byte { data, err := runtime.Encode(scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion), obj) if err != nil {

expectedErr string }{ { <ins> name: "empty", obj: &corev1.PodList{ Items: []corev1.Pod{}, }, sort: &corev1.PodList{ Items: []corev1.Pod{}, }, field: "{.metadata.name}", }, { </ins> name: "in-order-already", obj: &corev1.PodList{ Items: []corev1.Pod{

name: "v1.List in order", obj: &corev1.List{ Items: []runtime.RawExtension{ <del> {Raw: encodeOrDie(a)}, {Raw: encodeOrDie(b)}, {Raw: encodeOrDie(c)}, </del> <ins> {Object: a, Raw: encodeOrDie(a)}, {Object: b, Raw: encodeOrDie(b)}, {Object: c, Raw: encodeOrDie(c)}, </ins> }, }, sort: &corev1.List{ Items: []runtime.RawExtension{ <del> {Raw: encodeOrDie(a)}, {Raw: encodeOrDie(b)}, {Raw: encodeOrDie(c)}, </del> <ins> {Object: a, Raw: encodeOrDie(a)}, {Object: b, Raw: encodeOrDie(b)}, {Object: c, Raw: encodeOrDie(c)}, </ins> }, }, field: "{.metadata.name}",

name: "v1.List in reverse", obj: &corev1.List{ Items: []runtime.RawExtension{ <del> {Raw: encodeOrDie(c)}, {Raw: encodeOrDie(b)}, {Raw: encodeOrDie(a)}, </del> <ins> {Object: c, Raw: encodeOrDie(c)}, {Object: b, Raw: encodeOrDie(b)}, {Object: a, Raw: encodeOrDie(a)}, </ins> }, }, sort: &corev1.List{ Items: []runtime.RawExtension{ <del> {Raw: encodeOrDie(a)}, {Raw: encodeOrDie(b)}, {Raw: encodeOrDie(c)}, </del> <ins> {Object: a, Raw: encodeOrDie(a)}, {Object: b, Raw: encodeOrDie(b)}, {Object: c, Raw: encodeOrDie(c)}, </ins> }, }, field: "{.metadata.name}",

}, } for _, tt := range tests { <ins> t.Run(tt.name+" table", func(t *testing.T) { table := &metav1beta1.Table{} meta.EachListItem(tt.obj, func(item runtime.Object) error { table.Rows = append(table.Rows, metav1beta1.TableRow{ Object: runtime.RawExtension{Object: toUnstructuredOrDie(encodeOrDie(item))}, }) return nil }) expectedTable := &metav1beta1.Table{} meta.EachListItem(tt.sort, func(item runtime.Object) error { expectedTable.Rows = append(expectedTable.Rows, metav1beta1.TableRow{ Object: runtime.RawExtension{Object: toUnstructuredOrDie(encodeOrDie(item))}, }) return nil }) sorter, err := NewTableSorter(table, tt.field) if err == nil { err = sorter.Sort() } if err != nil { if len(tt.expectedErr) > 0 { if strings.Contains(err.Error(), tt.expectedErr) { return } t.Fatalf("%s: expected error containing: %q, got: "%v"", tt.name, tt.expectedErr, err) } t.Fatalf("%s: unexpected error: %v", tt.name, err) } if len(tt.expectedErr) > 0 { t.Fatalf("%s: expected error containing: %q, got none", tt.name, tt.expectedErr) } if !reflect.DeepEqual(table, expectedTable) { t.Errorf("[%s]nexpected/saw:n%s", tt.name, diff.ObjectReflectDiff(expectedTable, table)) } }) </ins> t.Run(tt.name, func(t *testing.T) { sort := &SortingPrinter{SortField: tt.field, Decoder: scheme.Codecs.UniversalDecoder()} err := sort.sortObj(tt.obj)