mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	kubectl: move custom columns printers and flags
This commit is contained in:
		
							
								
								
									
										373
									
								
								pkg/kubectl/cmd/get/sorter.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								pkg/kubectl/cmd/get/sorter.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,373 @@ | ||||
| /* | ||||
| Copyright 2014 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 get | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"io" | ||||
| 	"reflect" | ||||
| 	"sort" | ||||
|  | ||||
| 	"k8s.io/klog" | ||||
|  | ||||
| 	corev1 "k8s.io/api/core/v1" | ||||
| 	"k8s.io/apimachinery/pkg/api/meta" | ||||
| 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||
| 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" | ||||
| 	metav1beta1 "k8s.io/apimachinery/pkg/apis/meta/v1beta1" | ||||
| 	"k8s.io/apimachinery/pkg/runtime" | ||||
| 	"k8s.io/cli-runtime/pkg/genericclioptions/printers" | ||||
| 	"k8s.io/client-go/util/integer" | ||||
| 	"k8s.io/client-go/util/jsonpath" | ||||
|  | ||||
| 	"vbom.ml/util/sortorder" | ||||
| ) | ||||
|  | ||||
| // SortingPrinter sorts list types before delegating to another printer. | ||||
| // Non-list types are simply passed through | ||||
| type SortingPrinter struct { | ||||
| 	SortField string | ||||
| 	Delegate  printers.ResourcePrinter | ||||
| 	Decoder   runtime.Decoder | ||||
| } | ||||
|  | ||||
| func (s *SortingPrinter) PrintObj(obj runtime.Object, out io.Writer) error { | ||||
| 	if !meta.IsListType(obj) { | ||||
| 		return s.Delegate.PrintObj(obj, out) | ||||
| 	} | ||||
|  | ||||
| 	if err := s.sortObj(obj); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return s.Delegate.PrintObj(obj, out) | ||||
| } | ||||
|  | ||||
| func (s *SortingPrinter) sortObj(obj runtime.Object) error { | ||||
| 	objs, err := meta.ExtractList(obj) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if len(objs) == 0 { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	sorter, err := SortObjects(s.Decoder, objs, s.SortField) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	switch list := obj.(type) { | ||||
| 	case *corev1.List: | ||||
| 		outputList := make([]runtime.RawExtension, len(objs)) | ||||
| 		for ix := range objs { | ||||
| 			outputList[ix] = list.Items[sorter.OriginalPosition(ix)] | ||||
| 		} | ||||
| 		list.Items = outputList | ||||
| 		return nil | ||||
| 	} | ||||
| 	return meta.SetList(obj, objs) | ||||
| } | ||||
|  | ||||
| func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput string) (*RuntimeSort, error) { | ||||
| 	for ix := range objs { | ||||
| 		item := objs[ix] | ||||
| 		switch u := item.(type) { | ||||
| 		case *runtime.Unknown: | ||||
| 			var err error | ||||
| 			// decode runtime.Unknown to runtime.Unstructured for sorting. | ||||
| 			// we don't actually want the internal versions of known types. | ||||
| 			if objs[ix], _, err = decoder.Decode(u.Raw, nil, &unstructured.Unstructured{}); err != nil { | ||||
| 				return nil, err | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	field, err := RelaxedJSONPathExpression(fieldInput) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	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 { | ||||
| 		values, err := findJSONPathResults(parser, obj) | ||||
| 		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) | ||||
| 	sort.Sort(sorter) | ||||
| 	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 | ||||
| 	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 { | ||||
| 	return len(r.objs) | ||||
| } | ||||
|  | ||||
| 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) { | ||||
| 	switch i.Kind() { | ||||
| 	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: | ||||
| 		return i.Int() < j.Int(), nil | ||||
| 	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: | ||||
| 		return i.Uint() < j.Uint(), nil | ||||
| 	case reflect.Float32, reflect.Float64: | ||||
| 		return i.Float() < j.Float(), nil | ||||
| 	case reflect.String: | ||||
| 		return sortorder.NaturalLess(i.String(), j.String()), nil | ||||
| 	case reflect.Ptr: | ||||
| 		return isLess(i.Elem(), j.Elem()) | ||||
| 	case reflect.Struct: | ||||
| 		// sort metav1.Time | ||||
| 		in := i.Interface() | ||||
| 		if t, ok := in.(metav1.Time); ok { | ||||
| 			time := j.Interface().(metav1.Time) | ||||
| 			return t.Before(&time), nil | ||||
| 		} | ||||
| 		// fallback to the fields comparison | ||||
| 		for idx := 0; idx < i.NumField(); idx++ { | ||||
| 			less, err := isLess(i.Field(idx), j.Field(idx)) | ||||
| 			if err != nil || !less { | ||||
| 				return less, err | ||||
| 			} | ||||
| 		} | ||||
| 		return true, nil | ||||
| 	case reflect.Array, reflect.Slice: | ||||
| 		// note: the length of i and j may be different | ||||
| 		for idx := 0; idx < integer.IntMin(i.Len(), j.Len()); idx++ { | ||||
| 			less, err := isLess(i.Index(idx), j.Index(idx)) | ||||
| 			if err != nil || !less { | ||||
| 				return less, err | ||||
| 			} | ||||
| 		} | ||||
| 		return true, nil | ||||
|  | ||||
| 	case reflect.Interface: | ||||
| 		switch itype := i.Interface().(type) { | ||||
| 		case uint8: | ||||
| 			if jtype, ok := j.Interface().(uint8); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case uint16: | ||||
| 			if jtype, ok := j.Interface().(uint16); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case uint32: | ||||
| 			if jtype, ok := j.Interface().(uint32); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case uint64: | ||||
| 			if jtype, ok := j.Interface().(uint64); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case int8: | ||||
| 			if jtype, ok := j.Interface().(int8); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case int16: | ||||
| 			if jtype, ok := j.Interface().(int16); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case int32: | ||||
| 			if jtype, ok := j.Interface().(int32); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case int64: | ||||
| 			if jtype, ok := j.Interface().(int64); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case uint: | ||||
| 			if jtype, ok := j.Interface().(uint); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case int: | ||||
| 			if jtype, ok := j.Interface().(int); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case float32: | ||||
| 			if jtype, ok := j.Interface().(float32); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case float64: | ||||
| 			if jtype, ok := j.Interface().(float64); ok { | ||||
| 				return itype < jtype, nil | ||||
| 			} | ||||
| 		case string: | ||||
| 			if jtype, ok := j.Interface().(string); ok { | ||||
| 				return sortorder.NaturalLess(itype, jtype), nil | ||||
| 			} | ||||
| 		default: | ||||
| 			return false, fmt.Errorf("unsortable type: %T", itype) | ||||
| 		} | ||||
| 		return false, fmt.Errorf("unsortable interface: %v", i.Kind()) | ||||
|  | ||||
| 	default: | ||||
| 		return false, fmt.Errorf("unsortable type: %v", i.Kind()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *RuntimeSort) Less(i, j int) bool { | ||||
| 	iObj := r.objs[i] | ||||
| 	jObj := r.objs[j] | ||||
|  | ||||
| 	var iValues [][]reflect.Value | ||||
| 	var jValues [][]reflect.Value | ||||
| 	var err error | ||||
|  | ||||
| 	parser := jsonpath.New("sorting").AllowMissingKeys(true) | ||||
| 	err = parser.Parse(r.field) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
|  | ||||
| 	iValues, err = findJSONPathResults(parser, iObj) | ||||
| 	if err != nil { | ||||
| 		klog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err) | ||||
| 	} | ||||
|  | ||||
| 	jValues, err = findJSONPathResults(parser, jObj) | ||||
| 	if err != nil { | ||||
| 		klog.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] | ||||
| 	jField := jValues[0][0] | ||||
|  | ||||
| 	less, err := isLess(iField, jField) | ||||
| 	if err != nil { | ||||
| 		klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", r.field, iObj, iField.Kind().String(), err) | ||||
| 	} | ||||
| 	return less | ||||
| } | ||||
|  | ||||
| // OriginalPosition 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] | ||||
| } | ||||
|  | ||||
| type TableSorter struct { | ||||
| 	field      string | ||||
| 	obj        *metav1beta1.Table | ||||
| 	parsedRows [][][]reflect.Value | ||||
| } | ||||
|  | ||||
| func (t *TableSorter) Len() int { | ||||
| 	return len(t.obj.Rows) | ||||
| } | ||||
|  | ||||
| func (t *TableSorter) Swap(i, j int) { | ||||
| 	t.obj.Rows[i], t.obj.Rows[j] = t.obj.Rows[j], t.obj.Rows[i] | ||||
| } | ||||
|  | ||||
| func (t *TableSorter) Less(i, j int) bool { | ||||
| 	iValues := t.parsedRows[i] | ||||
| 	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) | ||||
| 	} | ||||
|  | ||||
| 	iField := iValues[0][0] | ||||
| 	jField := jValues[0][0] | ||||
|  | ||||
| 	less, err := isLess(iField, jField) | ||||
| 	if err != nil { | ||||
| 		klog.Fatalf("Field %s in %T is an unsortable type: %s, err: %v", t.field, t.parsedRows, iField.Kind().String(), err) | ||||
| 	} | ||||
| 	return less | ||||
| } | ||||
|  | ||||
| func (t *TableSorter) Sort() error { | ||||
| 	sort.Sort(t) | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func NewTableSorter(table *metav1beta1.Table, field string) *TableSorter { | ||||
| 	var parsedRows [][][]reflect.Value | ||||
|  | ||||
| 	parser := jsonpath.New("sorting").AllowMissingKeys(true) | ||||
| 	err := parser.Parse(field) | ||||
| 	if err != nil { | ||||
| 		klog.Fatalf("sorting error: %v\n", err) | ||||
| 	} | ||||
|  | ||||
| 	for i := range table.Rows { | ||||
| 		parsedRow, err := findJSONPathResults(parser, table.Rows[i].Object.Object) | ||||
| 		if err != nil { | ||||
| 			klog.Fatalf("Failed to get values for %#v using %s (%#v)", parsedRow, field, err) | ||||
| 		} | ||||
| 		parsedRows = append(parsedRows, parsedRow) | ||||
| 	} | ||||
|  | ||||
| 	return &TableSorter{ | ||||
| 		obj:        table, | ||||
| 		field:      field, | ||||
| 		parsedRows: parsedRows, | ||||
| 	} | ||||
| } | ||||
| func findJSONPathResults(parser *jsonpath.JSONPath, from runtime.Object) ([][]reflect.Value, error) { | ||||
| 	if unstructuredObj, ok := from.(*unstructured.Unstructured); ok { | ||||
| 		return parser.FindResults(unstructuredObj.Object) | ||||
| 	} | ||||
| 	return parser.FindResults(reflect.ValueOf(from).Elem().Interface()) | ||||
| } | ||||
		Reference in New Issue
	
	Block a user