Skip to main content
Glama
list.go6.33 kB
package callbacks import ( "fmt" "reflect" "strings" "github.com/duke-git/lancet/v2/slice" "github.com/duke-git/lancet/v2/stream" "github.com/weibaohui/kom/kom" "github.com/weibaohui/kom/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" "k8s.io/klog/v2" ) func ConvertUnstructuredItems(src *unstructured.UnstructuredList) []*unstructured.Unstructured { var dstItems []*unstructured.Unstructured for i := range src.Items { // 注意:使用 &src.Items[i] 是安全的,因为我们遍历的是 index,而不是 range 中的值副本 dstItems = append(dstItems, &src.Items[i]) } return dstItems } func List(k *kom.Kubectl) error { stmt := k.Statement gvr := stmt.GVR namespaced := stmt.Namespaced ns := stmt.Namespace ctx := stmt.Context conditions := stmt.Filter.Conditions namespaceList := stmt.NamespaceList opts := stmt.ListOptions listOptions := metav1.ListOptions{} listOptionsMD5 := "" // cache key值 if len(opts) > 0 { listOptions = opts[0] // 将listOptions序列化为JSON字符串,并取MD5摘要,加入cacheKey listOptionsStr := utils.ToJSON(listOptions) listOptionsMD5 = utils.MD5Hash(listOptionsStr) } // 使用反射获取 dest 的值 destValue := reflect.ValueOf(stmt.Dest) // 确保 dest 是一个指向切片的指针 if destValue.Kind() != reflect.Ptr || destValue.Elem().Kind() != reflect.Slice { // 处理错误:dest 不是指向切片的指针 return fmt.Errorf("请传入数组类型") } // 获取切片的元素类型 elemType := destValue.Elem().Type().Elem() cacheKey := fmt.Sprintf("%s/%s/%s/%s/%s", ns, gvr.Group, gvr.Resource, gvr.Version, listOptionsMD5) list, err := utils.GetOrSetCache(stmt.ClusterCache(), cacheKey, stmt.CacheTTL, func() (list *unstructured.UnstructuredList, err error) { // TODO 获取列表改为使用Option,解决大数据量获取问题。 if namespaced { if stmt.AllNamespace || len(namespaceList) > 1 { // 全部命名空间 或者 传入多个命名空间 // client-go 不支持跨命名空间查询,就全部查出来,后面再过滤 ns = metav1.NamespaceAll list, err = stmt.Kubectl.DynamicClient().Resource(gvr).Namespace(ns).List(ctx, listOptions) } else { // 不是全部,也没有传多个命名空间 if ns == "" { ns = metav1.NamespaceDefault } list, err = stmt.Kubectl.DynamicClient().Resource(gvr).Namespace(ns).List(ctx, listOptions) } } else { // 集群级查询,不需要namespace list, err = stmt.Kubectl.DynamicClient().Resource(gvr).List(ctx, listOptions) } return }) if err != nil { return err } if list == nil { // 为空直接返回 return fmt.Errorf("list is nil") } if list.Items == nil { // 为空直接返回 return fmt.Errorf("list Items is nil") } items := ConvertUnstructuredItems(list) // 对结果进行过滤,执行where 条件 result := executeFilter(items, conditions) if stmt.TotalCount != nil { *stmt.TotalCount = int64(len(result)) } if stmt.Filter.Order != "" { // 对结果执行OrderBy klog.V(6).Infof("order by = %s", stmt.Filter.Order) executeOrderBy(result, stmt.Filter.Order) } else { // 默认按创建时间倒序 utils.SortByCreationTime(result) } // 先清空之前的值 destValue.Elem().Set(reflect.MakeSlice(destValue.Elem().Type(), 0, 0)) streamTmp := stream.FromSlice(result) // 查看是否有filter ,先使用filter 形成一个最终的list.Items if stmt.Filter.Offset > 0 { streamTmp = streamTmp.Skip(stmt.Filter.Offset) } if stmt.Filter.Limit > 0 { streamTmp = streamTmp.Limit(stmt.Filter.Limit) } for _, item := range streamTmp.ToSlice() { obj := item.DeepCopy() if stmt.RemoveManagedFields { utils.RemoveManagedFields(obj) } // 创建新的指向元素类型的指针 newElemPtr := reflect.New(elemType) // unstructured 转换为原始目标类型 err = runtime.DefaultUnstructuredConverter.FromUnstructured(obj.Object, newElemPtr.Interface()) // 将指针的值添加到切片中 destValue.Elem().Set(reflect.Append(destValue.Elem(), newElemPtr.Elem())) } stmt.RowsAffected = int64(len(list.Items)) if err != nil { return err } return nil } func executeOrderBy(result []*unstructured.Unstructured, order string) { // order by `metadata.name` asc, `metadata.host` asc // todo 目前只实现了单一字段的排序,还没有搞定多个字段的排序 order = strings.TrimPrefix(strings.TrimSpace(order), "order by") order = strings.TrimSpace(order) orders := strings.Split(order, ",") for _, ord := range orders { var field string var desc bool // 判断排序方向 if strings.Contains(ord, "desc") { desc = true field = strings.ReplaceAll(ord, "desc", "") } else { field = strings.ReplaceAll(ord, "asc", "") } field = strings.TrimSpace(field) field = strings.TrimSpace(utils.TrimQuotes(field)) klog.V(6).Infof("Sorting by field: %s, Desc: %v", field, desc) slice.SortBy(result, func(a, b *unstructured.Unstructured) bool { // 获取字段值 aFieldValues, found, err := getNestedFieldAsString(a.Object, field) if err != nil || !found { return false } bFieldValues, found, err := getNestedFieldAsString(b.Object, field) if err != nil || !found { return false } // order by 必须把数组变为单一的值 if len(aFieldValues) > 1 || len(bFieldValues) > 1 { return false } if len(aFieldValues) == 0 || len(bFieldValues) == 0 { return false } aFieldValue := aFieldValues[0] bFieldValue := bFieldValues[0] t, va := utils.DetectType(aFieldValue) _, vb := utils.DetectType(bFieldValue) switch t { case utils.TypeString: if desc { return va.(string) > vb.(string) } return va.(string) < vb.(string) case utils.TypeNumber: if desc { return va.(float64) > vb.(float64) } return va.(float64) < vb.(float64) case utils.TypeTime: tva, err := utils.ParseTime(fmt.Sprintf("%s", va)) if err != nil { return false } tvb, err := utils.ParseTime(fmt.Sprintf("%s", vb)) if err != nil { return false } if desc { return tva.After(tvb) } return tva.Before(tvb) default: return false } }) } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/weibaohui/kom'

If you have feedback or need assistance with the MCP directory API, please join our Discord server