Fix sort-by regression

pull/58/head
Jordan Liggitt 2018-12-06 11:21:40 -05:00
parent ad728da0e6
commit 2b13dd87af
3 changed files with 93 additions and 20 deletions

View File

@ -334,8 +334,10 @@ func (r *RuntimeSorter) Sort() error {
case *metav1beta1.Table: case *metav1beta1.Table:
includesTable = true includesTable = true
if err := NewTableSorter(t, r.field).Sort(); err != nil { if sorter, err := NewTableSorter(t, r.field); err != nil {
continue return err
} else if err := sorter.Sort(); err != nil {
return err
} }
default: default:
includesRuntimeObjs = true includesRuntimeObjs = true

View File

@ -318,13 +318,18 @@ func (t *TableSorter) Len() int {
func (t *TableSorter) Swap(i, j int) { func (t *TableSorter) Swap(i, j int) {
t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i] t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i]
t.parsedRows[i], t.parsedRows[j] = t.parsedRows[j], t.parsedRows[i]
} }
func (t *TableSorter) Less(i, j int) bool { func (t *TableSorter) Less(i, j int) bool {
iValues := t.parsedRows[i] iValues := t.parsedRows[i]
jValues := t.parsedRows[j] jValues := t.parsedRows[j]
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) if len(iValues) == 0 || len(iValues[0]) == 0 {
return true
}
if len(jValues) == 0 || len(jValues[0]) == 0 {
return false
} }
iField := iValues[0][0] iField := iValues[0][0]
@ -342,28 +347,36 @@ func (t *TableSorter) Sort() error {
return nil return nil
} }
func NewTableSorter(table *metav1beta1.Table, field string) *TableSorter { func NewTableSorter(table *metav1beta1.Table, field string) (*TableSorter, error) {
var parsedRows [][][]reflect.Value var parsedRows [][][]reflect.Value
parser := jsonpath.New("sorting").AllowMissingKeys(true) parser := jsonpath.New("sorting").AllowMissingKeys(true)
err := parser.Parse(field) err := parser.Parse(field)
if err != nil { if err != nil {
klog.Fatalf("sorting error: %v\n", err) return nil, fmt.Errorf("sorting error: %v", err)
} }
fieldFoundOnce := false
for i := range table.Rows { for i := range table.Rows {
parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object) parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object)
if err != nil { if err != nil {
klog.Fatalf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err) return nil, fmt.Errorf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err)
} }
parsedRows = append(parsedRows, parsedRow) parsedRows = append(parsedRows, parsedRow)
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)
} }
return &TableSorter{ return &TableSorter{
obj: table, obj: table,
field: field, field: field,
parsedRows: parsedRows, parsedRows: parsedRows,
} }, nil
} }
func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) { func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) {
if unstructuredObj, ok := from.(*unstructured.Unstructured); ok { if unstructuredObj, ok := from.(*unstructured.Unstructured); ok {

View File

@ -17,6 +17,7 @@ limitations under the License.
package get package get
import ( import (
"encoding/json"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -25,10 +26,20 @@ import (
"k8s.io/apimachinery/pkg/api/meta" "k8s.io/apimachinery/pkg/api/meta"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/diff"
"k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/scheme"
) )
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}
}
func encodeOrDie(obj runtime.Object) []byte { func encodeOrDie(obj runtime.Object) []byte {
data, err := runtime.Encode(scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion), obj) data, err := runtime.Encode(scheme.Codecs.LegacyCodec(corev1.SchemeGroupVersion), obj)
if err != nil { if err != nil {
@ -65,6 +76,16 @@ func TestSortingPrinter(t *testing.T) {
name string name string
expectedErr string expectedErr string
}{ }{
{
name: "empty",
obj: &corev1.PodList{
Items: []corev1.Pod{},
},
sort: &corev1.PodList{
Items: []corev1.Pod{},
},
field: "{.metadata.name}",
},
{ {
name: "in-order-already", name: "in-order-already",
obj: &corev1.PodList{ obj: &corev1.PodList{
@ -237,16 +258,16 @@ func TestSortingPrinter(t *testing.T) {
name: "v1.List in order", name: "v1.List in order",
obj: &corev1.List{ obj: &corev1.List{
Items: []runtime.RawExtension{ Items: []runtime.RawExtension{
{Raw: encodeOrDie(a)}, {Object: a, Raw: encodeOrDie(a)},
{Raw: encodeOrDie(b)}, {Object: b, Raw: encodeOrDie(b)},
{Raw: encodeOrDie(c)}, {Object: c, Raw: encodeOrDie(c)},
}, },
}, },
sort: &corev1.List{ sort: &corev1.List{
Items: []runtime.RawExtension{ Items: []runtime.RawExtension{
{Raw: encodeOrDie(a)}, {Object: a, Raw: encodeOrDie(a)},
{Raw: encodeOrDie(b)}, {Object: b, Raw: encodeOrDie(b)},
{Raw: encodeOrDie(c)}, {Object: c, Raw: encodeOrDie(c)},
}, },
}, },
field: "{.metadata.name}", field: "{.metadata.name}",
@ -255,16 +276,16 @@ func TestSortingPrinter(t *testing.T) {
name: "v1.List in reverse", name: "v1.List in reverse",
obj: &corev1.List{ obj: &corev1.List{
Items: []runtime.RawExtension{ Items: []runtime.RawExtension{
{Raw: encodeOrDie(c)}, {Object: c, Raw: encodeOrDie(c)},
{Raw: encodeOrDie(b)}, {Object: b, Raw: encodeOrDie(b)},
{Raw: encodeOrDie(a)}, {Object: a, Raw: encodeOrDie(a)},
}, },
}, },
sort: &corev1.List{ sort: &corev1.List{
Items: []runtime.RawExtension{ Items: []runtime.RawExtension{
{Raw: encodeOrDie(a)}, {Object: a, Raw: encodeOrDie(a)},
{Raw: encodeOrDie(b)}, {Object: b, Raw: encodeOrDie(b)},
{Raw: encodeOrDie(c)}, {Object: c, Raw: encodeOrDie(c)},
}, },
}, },
field: "{.metadata.name}", field: "{.metadata.name}",
@ -390,6 +411,43 @@ func TestSortingPrinter(t *testing.T) {
}, },
} }
for _, tt := range tests { for _, tt := range tests {
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))
}
})
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
sort := &SortingPrinter{SortField: tt.field, Decoder: scheme.Codecs.UniversalDecoder()} sort := &SortingPrinter{SortField: tt.field, Decoder: scheme.Codecs.UniversalDecoder()}
err := sort.sortObj(tt.obj) err := sort.sortObj(tt.obj)