mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-11 13:02:14 +00:00
Fix sorting printer with missing fields
This commit is contained in:
parent
0107e93cc6
commit
5eeef81edf
@ -174,6 +174,7 @@ go_test(
|
|||||||
"//vendor:github.com/spf13/cobra",
|
"//vendor:github.com/spf13/cobra",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
"//vendor:k8s.io/apimachinery/pkg/api/errors",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1",
|
||||||
|
"//vendor:k8s.io/apimachinery/pkg/apis/meta/v1/unstructured",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
"//vendor:k8s.io/apimachinery/pkg/runtime",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
"//vendor:k8s.io/apimachinery/pkg/runtime/schema",
|
||||||
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/yaml",
|
"//vendor:k8s.io/apimachinery/pkg/runtime/serializer/yaml",
|
||||||
|
@ -88,17 +88,6 @@ func (s *SortingPrinter) sortObj(obj runtime.Object) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) {
|
func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) {
|
||||||
parser := jsonpath.New("sorting")
|
|
||||||
|
|
||||||
field, err := massageJSONPath(fieldInput)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := parser.Parse(field); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for ix := range objs {
|
for ix := range objs {
|
||||||
item := objs[ix]
|
item := objs[ix]
|
||||||
switch u := item.(type) {
|
switch u := item.(type) {
|
||||||
@ -112,18 +101,38 @@ func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var values [][]reflect.Value
|
field, err := massageJSONPath(fieldInput)
|
||||||
if unstructured, ok := objs[0].(*unstructured.Unstructured); ok {
|
|
||||||
values, err = parser.FindResults(unstructured.Object)
|
|
||||||
} else {
|
|
||||||
values, err = parser.FindResults(reflect.ValueOf(objs[0]).Elem().Interface())
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(values) == 0 {
|
|
||||||
return nil, fmt.Errorf("couldn't find any field with path: %s", field)
|
parser := jsonpath.New("sorting").AllowMissingKeys(true)
|
||||||
|
if err := parser.Parse(field); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't do any model validation here, so we traverse all objects to be sorted
|
||||||
|
// and, if the field is valid to at least one of them, we consider it to be a
|
||||||
|
// valid field; otherwise error out.
|
||||||
|
// Note that this requires empty fields to be considered later, when sorting.
|
||||||
|
var fieldFoundOnce bool
|
||||||
|
for _, obj := range objs {
|
||||||
|
var values [][]reflect.Value
|
||||||
|
if unstructured, ok := obj.(*unstructured.Unstructured); ok {
|
||||||
|
values, err = parser.FindResults(unstructured.Object)
|
||||||
|
} else {
|
||||||
|
values, err = parser.FindResults(reflect.ValueOf(obj).Elem().Interface())
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(values) > 0 && len(values[0]) > 0 {
|
||||||
|
fieldFoundOnce = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fieldFoundOnce {
|
||||||
|
return nil, fmt.Errorf("couldn't find any field with path %q in the list of objects", field)
|
||||||
}
|
}
|
||||||
|
|
||||||
sorter := NewRuntimeSort(field, objs)
|
sorter := NewRuntimeSort(field, objs)
|
||||||
@ -260,7 +269,7 @@ func (r *RuntimeSort) Less(i, j int) bool {
|
|||||||
iObj := r.objs[i]
|
iObj := r.objs[i]
|
||||||
jObj := r.objs[j]
|
jObj := r.objs[j]
|
||||||
|
|
||||||
parser := jsonpath.New("sorting")
|
parser := jsonpath.New("sorting").AllowMissingKeys(true)
|
||||||
parser.Parse(r.field)
|
parser.Parse(r.field)
|
||||||
|
|
||||||
var iValues [][]reflect.Value
|
var iValues [][]reflect.Value
|
||||||
@ -285,12 +294,18 @@ func (r *RuntimeSort) Less(i, j int) bool {
|
|||||||
glog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err)
|
glog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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]
|
||||||
jField := jValues[0][0]
|
jField := jValues[0][0]
|
||||||
|
|
||||||
less, err := isLess(iField, jField)
|
less, err := isLess(iField, jField)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Fatalf("Field %s in %v is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err)
|
glog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err)
|
||||||
}
|
}
|
||||||
return less
|
return less
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,11 @@ package kubectl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
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/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
internal "k8s.io/kubernetes/pkg/api"
|
internal "k8s.io/kubernetes/pkg/api"
|
||||||
api "k8s.io/kubernetes/pkg/api/v1"
|
api "k8s.io/kubernetes/pkg/api/v1"
|
||||||
@ -56,10 +58,11 @@ func TestSortingPrinter(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
obj runtime.Object
|
obj runtime.Object
|
||||||
sort runtime.Object
|
sort runtime.Object
|
||||||
field string
|
field string
|
||||||
name string
|
name string
|
||||||
|
expectedErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "in-order-already",
|
name: "in-order-already",
|
||||||
@ -265,12 +268,140 @@ func TestSortingPrinter(t *testing.T) {
|
|||||||
},
|
},
|
||||||
field: "{.metadata.name}",
|
field: "{.metadata.name}",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "some-missing-fields",
|
||||||
|
obj: &unstructured.UnstructuredList{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "List",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
},
|
||||||
|
Items: []*unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{
|
||||||
|
"availableReplicas": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{
|
||||||
|
"availableReplicas": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sort: &unstructured.UnstructuredList{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "List",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
},
|
||||||
|
Items: []*unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{
|
||||||
|
"availableReplicas": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{
|
||||||
|
"availableReplicas": 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: "{.status.availableReplicas}",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "all-missing-fields",
|
||||||
|
obj: &unstructured.UnstructuredList{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "List",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
},
|
||||||
|
Items: []*unstructured.Unstructured{
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{
|
||||||
|
"replicas": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: map[string]interface{}{
|
||||||
|
"kind": "ReplicationController",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"status": map[string]interface{}{
|
||||||
|
"replicas": 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: "{.status.availableReplicas}",
|
||||||
|
expectedErr: "couldn't find any field with path \"{.status.availableReplicas}\" in the list of objects",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "model-invalid-fields",
|
||||||
|
obj: &api.ReplicationControllerList{
|
||||||
|
Items: []api.ReplicationController{
|
||||||
|
{
|
||||||
|
Status: api.ReplicationControllerStatus{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Status: api.ReplicationControllerStatus{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Status: api.ReplicationControllerStatus{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
field: "{.invalid}",
|
||||||
|
expectedErr: "couldn't find any field with path \"{.invalid}\" in the list of objects",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
sort := &SortingPrinter{SortField: test.field, Decoder: internal.Codecs.UniversalDecoder()}
|
sort := &SortingPrinter{SortField: test.field, Decoder: internal.Codecs.UniversalDecoder()}
|
||||||
if err := sort.sortObj(test.obj); err != nil {
|
err := sort.sortObj(test.obj)
|
||||||
t.Errorf("unexpected error: %v (%s)", err, test.name)
|
if err != nil {
|
||||||
continue
|
if len(test.expectedErr) > 0 {
|
||||||
|
if strings.Contains(err.Error(), test.expectedErr) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Fatalf("%s: expected error containing: %q, got: \"%v\"", test.name, test.expectedErr, err)
|
||||||
|
}
|
||||||
|
t.Fatalf("%s: unexpected error: %v", test.name, err)
|
||||||
|
}
|
||||||
|
if len(test.expectedErr) > 0 {
|
||||||
|
t.Fatalf("%s: expected error containing: %q, got none", test.name, test.expectedErr)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(test.obj, test.sort) {
|
if !reflect.DeepEqual(test.obj, test.sort) {
|
||||||
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v", test.name, test.sort, test.obj)
|
t.Errorf("[%s]\nexpected:\n%v\nsaw:\n%v", test.name, test.sort, test.obj)
|
||||||
|
Loading…
Reference in New Issue
Block a user