package v1beta1 import ( "encoding/json" "fmt" "sort" "strings" "github.com/oliveagle/jsonpath" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/selection" "k8s.io/klog/v2" "sigs.k8s.io/controller-runtime/pkg/client" "kubesphere.io/kubesphere/pkg/apiserver/query" ) type Interface interface { // Get retrieves a single object by its namespace and name Get(namespace, name string, object client.Object) error // List retrieves a collection of objects matches given query List(namespace string, query *query.Query, object client.ObjectList) error } type ResourceGetter interface { GetResource(schema.GroupVersionResource, string, string) (client.Object, error) ListResources(schema.GroupVersionResource, string, *query.Query) (client.ObjectList, error) } // CompareFunc return true is left great than right type CompareFunc func(runtime.Object, runtime.Object, query.Field) bool type FilterFunc func(runtime.Object, query.Filter) bool type TransformFunc func(runtime.Object) runtime.Object func DefaultList(objects []runtime.Object, q *query.Query, compareFunc CompareFunc, filterFunc FilterFunc, transformFuncs ...TransformFunc) []runtime.Object { // selected matched ones var filtered []runtime.Object if len(q.Filters) != 0 { for _, object := range objects { selected := true for field, value := range q.Filters { if !filterFunc(object, query.Filter{Field: field, Value: value}) { selected = false break } } if selected { for _, transform := range transformFuncs { object = transform(object) } filtered = append(filtered, object) } } } // sort by sortBy field sort.Slice(filtered, func(i, j int) bool { if !q.Ascending { return compareFunc(filtered[i], filtered[j], q.SortBy) } return !compareFunc(filtered[i], filtered[j], q.SortBy) }) total := len(filtered) if q.Pagination == nil { q.Pagination = query.NoPagination } start, end := q.Pagination.GetValidPagination(total) return filtered[start:end] } // DefaultObjectMetaCompare return true is left great than right func DefaultObjectMetaCompare(left, right metav1.Object, sortBy query.Field) bool { switch sortBy { // ?sortBy=name case query.FieldName: return strings.Compare(left.GetName(), right.GetName()) > 0 // ?sortBy=creationTimestamp default: fallthrough case query.FieldCreateTime: fallthrough case query.FieldCreationTimeStamp: // compare by name if creation timestamp is equal ltime := left.GetCreationTimestamp() rtime := right.GetCreationTimestamp() if ltime.Equal(&rtime) { return strings.Compare(left.GetName(), right.GetName()) > 0 } return left.GetCreationTimestamp().After(right.GetCreationTimestamp().Time) } } // Default metadata filter func DefaultObjectMetaFilter(item metav1.Object, filter query.Filter) bool { switch filter.Field { case query.FieldNames: for _, name := range strings.Split(string(filter.Value), ",") { if item.GetName() == name { return true } } return false // /namespaces?page=1&limit=10&name=default case query.FieldName: return strings.Contains(item.GetName(), string(filter.Value)) // /namespaces?page=1&limit=10&uid=a8a8d6cf-f6a5-4fea-9c1b-e57610115706 case query.FieldUID: return strings.Compare(string(item.GetUID()), string(filter.Value)) == 0 // /deployments?page=1&limit=10&namespace=kubesphere-system case query.FieldNamespace: return strings.Compare(item.GetNamespace(), string(filter.Value)) == 0 // /namespaces?page=1&limit=10&ownerReference=a8a8d6cf-f6a5-4fea-9c1b-e57610115706 case query.FieldOwnerReference: for _, ownerReference := range item.GetOwnerReferences() { if strings.Compare(string(ownerReference.UID), string(filter.Value)) == 0 { return true } } return false // /namespaces?page=1&limit=10&ownerKind=Workspace case query.FieldOwnerKind: for _, ownerReference := range item.GetOwnerReferences() { if strings.Compare(ownerReference.Kind, string(filter.Value)) == 0 { return true } } return false // /namespaces?page=1&limit=10&annotation=openpitrix_runtime case query.FieldAnnotation: return labelMatch(item.GetAnnotations(), string(filter.Value)) // /namespaces?page=1&limit=10&label=kubesphere.io/workspace:system-workspace case query.FieldLabel: return labelMatch(item.GetLabels(), string(filter.Value)) case query.ParameterFieldSelector: return contains(item.(runtime.Object), filter.Value) default: return false } } func labelMatch(labels map[string]string, filter string) bool { fields := strings.SplitN(filter, "=", 2) var key, value string var opposite bool if len(fields) == 2 { key = fields[0] if strings.HasSuffix(key, "!") { key = strings.TrimSuffix(key, "!") opposite = true } value = fields[1] } else { key = fields[0] value = "*" } for k, v := range labels { if opposite { if (k == key) && v != value { return true } } else { if (k == key) && (value == "*" || v == value) { return true } } } return false } // implement a generic query filter to support multiple field selectors with "jsonpath.JsonPathLookup" // https://github.com/oliveagle/jsonpath/blob/master/readme.md func contains(object runtime.Object, queryValue query.Value) bool { // call the ParseSelector function of "k8s.io/apimachinery/pkg/fields/selector.go" to validate and parse the selector fieldSelector, err := fields.ParseSelector(string(queryValue)) if err != nil { klog.V(4).Infof("failed parse selector error: %s", err) return false } for _, requirement := range fieldSelector.Requirements() { var negative bool // supports '=', '==' and '!='.(e.g. ?fieldSelector=key1=value1,key2=value2) // fields.ParseSelector(FieldSelector) has handled the case where the operator is '==' and converted it to '=', // so case selection.DoubleEquals can be ignored here. switch requirement.Operator { case selection.NotEquals: negative = true case selection.Equals: negative = false } key := requirement.Field value := requirement.Value var input map[string]interface{} data, err := json.Marshal(object) if err != nil { klog.V(4).Infof("failed marshal to JSON string: %s", err) return false } if err = json.Unmarshal(data, &input); err != nil { klog.V(4).Infof("failed unmarshal to map object: %s", err) return false } rawValue, err := jsonpath.JsonPathLookup(input, "$."+key) if err != nil { klog.V(4).Infof("failed to lookup jsonpath: %s", err) return false } if (negative && fmt.Sprintf("%v", rawValue) != value) || (!negative && fmt.Sprintf("%v", rawValue) == value) { continue } else { return false } } return true }