diff --git a/apiserver/pkg/application/application.go b/apiserver/pkg/application/application.go index 6b6a77c5f..61f9ec00f 100644 --- a/apiserver/pkg/application/application.go +++ b/apiserver/pkg/application/application.go @@ -20,7 +20,6 @@ import ( "context" "errors" "reflect" - "sort" "strings" k8serrors "k8s.io/apimachinery/pkg/api/errors" @@ -108,6 +107,10 @@ func cr2app(prompt *apiprompt.Prompt, chainConfig *apichain.CommonChainConfig, r return gApp, nil } +func app2metadataConverter(objApp *unstructured.Unstructured) (generated.PageNode, error) { + return app2metadata(objApp) +} + func app2metadata(objApp *unstructured.Unstructured) (*generated.ApplicationMetadata, error) { app := &v1alpha1.Application{} if err := runtime.DefaultUnstructuredConverter.FromUnstructured(objApp.UnstructuredContent(), app); err != nil { @@ -289,33 +292,11 @@ func ListApplicationMeatadatas(ctx context.Context, c dynamic.Interface, input g if err != nil { return nil, err } - sort.Slice(res.Items, func(i, j int) bool { - return res.Items[i].GetCreationTimestamp().After(res.Items[j].GetCreationTimestamp().Time) - }) - - filtered := make([]generated.PageNode, 0) - for _, u := range res.Items { - if keyword != "" { - displayName, _, _ := unstructured.NestedString(u.Object, "spec", "displayName") - if !strings.Contains(u.GetName(), keyword) && !strings.Contains(displayName, keyword) { - continue - } - } - m, err := app2metadata(&u) - if err != nil { - return nil, err - } - filtered = append(filtered, m) - } - totalCount := len(filtered) - start, end := common.PagePosition(page, pageSize, totalCount) - return &generated.PaginatedResult{ - TotalCount: totalCount, - HasNextPage: end < totalCount, - Nodes: filtered[start:end], - Page: &page, - PageSize: &pageSize, - }, nil + filter := make([]common.ResourceFilter, 0) + if keyword != "" { + filter = append(filter, common.FilterApplicationByKeyword(keyword)) + } + return common.ListReources(res, page, pageSize, app2metadataConverter, filter...) } func UpdateApplicationConfig(ctx context.Context, c dynamic.Interface, input generated.UpdateApplicationConfigInput) (*generated.Application, error) { diff --git a/apiserver/pkg/common/common_filter.go b/apiserver/pkg/common/common_filter.go new file mode 100644 index 000000000..46791c982 --- /dev/null +++ b/apiserver/pkg/common/common_filter.go @@ -0,0 +1,216 @@ +/* +Copyright 2024 KubeAGI. + +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 common + +import ( + "context" + "strings" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/dynamic" + + "github.com/kubeagi/arcadia/api/base/v1alpha1" + "github.com/kubeagi/arcadia/apiserver/graph/generated" +) + +type ( + ResourceFilter func(*unstructured.Unstructured) bool + ResourceConverter func(*unstructured.Unstructured) (generated.PageNode, error) +) + +func FilterByName(name string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + return u.GetName() == name + } +} + +// Dataset Filter +func FilterDatasetByDisplayName(displayName string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + ds := v1alpha1.Dataset{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &ds); err != nil { + return false + } + return ds.Spec.DisplayName == displayName + } +} + +func FilterDatasetByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + ds := v1alpha1.Dataset{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &ds); err != nil { + return false + } + if strings.Contains(ds.Name, keyword) { + return true + } + if strings.Contains(ds.Namespace, keyword) { + return true + } + if strings.Contains(ds.Spec.DisplayName, keyword) { + return true + } + if strings.Contains(ds.Spec.ContentType, keyword) { + return true + } + for _, v := range ds.Annotations { + if strings.Contains(v, keyword) { + return true + } + } + return false + } +} + +// Application +func FilterApplicationByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + displayName, _, _ := unstructured.NestedString(u.Object, "spec", "displayName") + return strings.Contains(displayName, keyword) || strings.Contains(u.GetName(), keyword) + } +} + +// Datasource +func FilterDatasourceByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + datasource := v1alpha1.Datasource{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &datasource); err != nil { + return false + } + return strings.Contains(datasource.Name, keyword) || strings.Contains(datasource.Spec.DisplayName, keyword) + } +} + +// Embedder +func FilterEmbedderByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + embedder := v1alpha1.Embedder{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &embedder); err != nil { + return false + } + return strings.Contains(embedder.Name, keyword) || strings.Contains(embedder.Spec.DisplayName, keyword) + } +} + +// KnowledgeBase +func FilterKnowledgeByName(name string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + kb := v1alpha1.KnowledgeBase{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &kb); err != nil { + return false + } + return strings.Contains(kb.Name, name) + } +} +func FilterKnowledgeByDisplayName(displayName string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + kb := v1alpha1.KnowledgeBase{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &kb); err != nil { + return false + } + return strings.Contains(kb.Spec.DisplayName, displayName) + } +} + +// LLM +func FilterLLMByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + llm := v1alpha1.LLM{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &llm); err != nil { + return false + } + return strings.Contains(llm.Name, keyword) || strings.Contains(llm.Spec.DisplayName, keyword) + } +} + +// Model +func FilterModelByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + model := v1alpha1.Model{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &model); err != nil { + return false + } + return strings.Contains(model.Name, keyword) || strings.Contains(model.Spec.DisplayName, keyword) + } +} + +// VersionedData +func FilterVersionedDatasetByDisplayName(displayName string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + v := v1alpha1.VersionedDataset{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &v); err != nil { + return false + } + return v.Spec.DisplayName == displayName + } +} + +func FilterVersionedDatasetByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + v := v1alpha1.VersionedDataset{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &v); err != nil { + return false + } + if strings.Contains(v.Name, keyword) { + return true + } + if strings.Contains(v.Namespace, keyword) { + return true + } + if strings.Contains(v.Spec.DisplayName, keyword) { + return true + } + for _, vv := range v.Annotations { + if strings.Contains(vv, keyword) { + return true + } + } + return false + } +} + +// Worekr +func FilterWorkerByKeyword(keyword string) ResourceFilter { + return func(u *unstructured.Unstructured) bool { + w := v1alpha1.Worker{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &w); err != nil { + return false + } + return strings.Contains(w.Name, keyword) || strings.Contains(w.Spec.DisplayName, keyword) + } +} + +func FilterWorkerByType(c dynamic.Interface, namespace, modelType string) ResourceFilter { + cache := make(map[string]string) + models, err := c.Resource(SchemaOf(&ArcadiaAPIGroup, "model")). + Namespace(namespace).List(context.Background(), v1.ListOptions{}) + if err == nil { + for _, m := range models.Items { + types, _, _ := unstructured.NestedString(m.Object, "spec", "types") + cache[m.GetName()] = types + } + } + return func(u *unstructured.Unstructured) bool { + w := v1alpha1.Worker{} + if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, &w); err != nil { + return false + } + // TODO: how do we do if the model and worek have different namespace? + return strings.Contains(cache[w.Spec.Model.Name], modelType) + } +} diff --git a/apiserver/pkg/common/common_list_resource.go b/apiserver/pkg/common/common_list_resource.go new file mode 100644 index 000000000..37d503543 --- /dev/null +++ b/apiserver/pkg/common/common_list_resource.go @@ -0,0 +1,70 @@ +/* +Copyright 2024 KubeAGI. + +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 common + +import ( + "container/heap" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/kubeagi/arcadia/apiserver/graph/generated" +) + +// ListReources filtering resources based on conditions will modify the original array, +// so if you want to preserve the original data, make a backup before calling the function +func ListReources(list *unstructured.UnstructuredList, page, pageSize int, converter ResourceConverter, options ...ResourceFilter) (*generated.PaginatedResult, error) { + index, optIndex := 0, 0 + for i := range list.Items { + optIndex = 0 + for ; optIndex < len(options); optIndex++ { + if !options[optIndex](&list.Items[i]) { + break + } + } + if optIndex == len(options) { + list.Items[index] = list.Items[i] + index++ + } + } + list.Items = list.Items[:index] + + total := len(list.Items) + start, end := PagePosition(page, pageSize, total) + result := &generated.PaginatedResult{ + TotalCount: total, + HasNextPage: end < total, + Nodes: make([]generated.PageNode, 0), + } + + if start >= total { + return result, nil + } + + h := PageNodeSorter(list.Items) + heap.Init(&h) + for cur := 0; h.Len() > 0 && cur < end; cur++ { + top := heap.Pop(&h).(unstructured.Unstructured) + if cur >= start { + node, err := converter(&top) + if err != nil { + return nil, err + } + result.Nodes = append(result.Nodes, node) + } + } + + return result, nil +} diff --git a/apiserver/pkg/common/common_list_resource_test.go b/apiserver/pkg/common/common_list_resource_test.go new file mode 100644 index 000000000..06a41c325 --- /dev/null +++ b/apiserver/pkg/common/common_list_resource_test.go @@ -0,0 +1,193 @@ +/* +Copyright 2024 KubeAGI. + +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 common + +import ( + "fmt" + "reflect" + "strings" + "testing" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/kubeagi/arcadia/apiserver/graph/generated" +) + +func filterByKeyWord(keyword string) func(*unstructured.Unstructured) bool { + return func(u *unstructured.Unstructured) bool { + return strings.Contains(u.GetName(), keyword) + } +} + +func converter(u *unstructured.Unstructured) (generated.PageNode, error) { + return &generated.F{Path: u.GetName()}, nil +} + +func initResource() unstructured.UnstructuredList { + // The reverse order should be + // name3, name7, name1, name5, name4, name2, name6,name9, name8 + timeStrings := []string{ + "2024-01-10T11:57:26Z", + "2024-01-05T11:57:26Z", + "2024-01-12T11:57:26Z", + "2024-01-08T11:57:26Z", + "2024-01-09T11:57:26Z", + "2024-01-02T11:57:26Z", + "2024-01-11T11:57:26Z", + "2023-01-12T11:57:26Z", + "2023-02-12T11:57:26Z", + } + l := unstructured.UnstructuredList{Items: make([]unstructured.Unstructured, 0)} + for i := 1; i < 10; i++ { + l.Items = append(l.Items, unstructured.Unstructured{ + Object: map[string]interface{}{ + "metadata": map[string]interface{}{ + "name": fmt.Sprintf("name%d", i), + "creationTimestamp": timeStrings[i-1], + }, + }, + }) + } + return l +} + +func toF(data []generated.PageNode) []string { + r := make([]string, 0) + for _, d := range data { + r = append(r, d.(*generated.F).Path) + } + return r +} + +func TestListResources(t *testing.T) { + type input struct { + keyword string + page, pageSize int + exp []string + } + for _, tc := range []input{ + { + keyword: "abc", + page: 1, + pageSize: 1, + exp: []string{}, + }, + { + keyword: "abc", + page: 1, + pageSize: 2, + exp: []string{}, + }, + { + keyword: "1", + page: 1, + pageSize: 1, + exp: []string{"name1"}, + }, + { + keyword: "1", + page: 1, + pageSize: 2, + exp: []string{"name1"}, + }, + { + keyword: "1", + page: 2, + pageSize: 2, + exp: []string{}, + }, + { + keyword: "name", + page: 1, + pageSize: 2, + exp: []string{"name3", "name7"}, + }, + { + keyword: "name", + page: 2, + pageSize: 2, + exp: []string{"name1", "name5"}, + }, + { + keyword: "name", + page: 3, + pageSize: 2, + exp: []string{"name4", "name2"}, + }, + { + keyword: "name", + page: 4, + pageSize: 2, + exp: []string{"name6", "name9"}, + }, + { + keyword: "name", + page: 5, + pageSize: 2, + exp: []string{"name8"}, + }, + { + keyword: "name", + page: 6, + pageSize: 2, + exp: []string{}, + }, + { + keyword: "name", + page: 1, + pageSize: 9, + exp: []string{"name3", "name7", "name1", "name5", "name4", "name2", "name6", "name9", "name8"}, + }, + { + keyword: "name1", + page: 1, + pageSize: 9, + exp: []string{"name1"}, + }, + { + keyword: "name", + page: 1, + pageSize: 8, + exp: []string{"name3", "name7", "name1", "name5", "name4", "name2", "name6", "name9"}, + }, + { + keyword: "name", + page: 2, + pageSize: 8, + exp: []string{"name8"}, + }, + { + keyword: "name", + page: 2, + pageSize: -1, + exp: []string{"name3", "name7", "name1", "name5", "name4", "name2", "name6", "name9", "name8"}, + }, + { + keyword: "name", + page: 3, + pageSize: 8, + exp: []string{}, + }, + } { + // name3, name7, name1, name5, name4, name2, name6,name9, name8 + data := initResource() + r, _ := ListReources(&data, tc.page, tc.pageSize, converter, filterByKeyWord(tc.keyword)) + r1 := toF(r.Nodes) + if !reflect.DeepEqual(tc.exp, r1) { + t.Fatalf("with keyword %s, page: %d, pageSize: %d , expect %v get %v", tc.keyword, tc.page, tc.pageSize, tc.exp, r1) + } + } +} diff --git a/apiserver/pkg/common/helper.go b/apiserver/pkg/common/helper.go new file mode 100644 index 000000000..e8e641225 --- /dev/null +++ b/apiserver/pkg/common/helper.go @@ -0,0 +1,67 @@ +/* +Copyright 2024 KubeAGI. + +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 common + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + + "github.com/kubeagi/arcadia/apiserver/config" +) + +type PageNodeSorter []unstructured.Unstructured + +func (p *PageNodeSorter) Len() int { + return len(*p) +} + +func (p *PageNodeSorter) Less(i, j int) bool { + sysmtemName := config.GetConfig().SystemNamespace + + nsa := (*p)[i].GetNamespace() + nsb := (*p)[j].GetNamespace() + a := (*p)[i].GetCreationTimestamp() + b := (*p)[j].GetCreationTimestamp() + + if nsa == nsb { + return a.After(b.Time) + } + + // this is for model ordering and requires the system model to come first + if nsa == sysmtemName { + return true + } + if nsb == sysmtemName { + return false + } + + return a.After(b.Time) +} + +func (p *PageNodeSorter) Swap(i, j int) { + (*p)[i], (*p)[j] = (*p)[j], (*p)[i] +} + +func (p *PageNodeSorter) Push(x any) { + *p = append(*p, x.(unstructured.Unstructured)) +} + +func (p *PageNodeSorter) Pop() any { + old := *p + l := len(old) + x := old[l-1] + *p = old[:l-1] + return x +} diff --git a/apiserver/pkg/dataset/dataset.go b/apiserver/pkg/dataset/dataset.go index 21e275a57..7f57b9575 100644 --- a/apiserver/pkg/dataset/dataset.go +++ b/apiserver/pkg/dataset/dataset.go @@ -19,8 +19,6 @@ package dataset import ( "context" "fmt" - "sort" - "strings" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -33,6 +31,10 @@ import ( "github.com/kubeagi/arcadia/apiserver/pkg/utils" ) +func dataset2modelConverter(obj *unstructured.Unstructured) (generated.PageNode, error) { + return dataset2model(obj) +} + func dataset2model(obj *unstructured.Unstructured) (*generated.Dataset, error) { ds := &generated.Dataset{} ds.Name = obj.GetName() @@ -116,9 +118,6 @@ func ListDatasets(ctx context.Context, c dynamic.Interface, input *generated.Lis if err != nil { return nil, err } - sort.Slice(datastList.Items, func(i, j int) bool { - return datastList.Items[i].GetCreationTimestamp().After(datastList.Items[j].GetCreationTimestamp().Time) - }) page, size := 1, 10 if input.Page != nil && *input.Page > 0 { page = *input.Page @@ -126,47 +125,14 @@ func ListDatasets(ctx context.Context, c dynamic.Interface, input *generated.Lis if input.PageSize != nil && *input.PageSize > 0 { size = *input.PageSize } - result := make([]generated.PageNode, 0) - for _, u := range datastList.Items { - uu, _ := dataset2model(&u) - if input.DisplayName != nil && *uu.DisplayName != *input.DisplayName { - continue - } - if input.Keyword != nil { - ok := false - if strings.Contains(uu.Name, *input.Keyword) { - ok = true - } - if strings.Contains(uu.Namespace, *input.Keyword) { - ok = true - } - if strings.Contains(*uu.DisplayName, *input.Keyword) { - ok = true - } - if strings.Contains(uu.ContentType, *input.Keyword) { - ok = true - } - for _, v := range uu.Annotations { - if strings.Contains(v.(string), *input.Keyword) { - ok = true - break - } - } - if !ok { - continue - } - } - result = append(result, uu) - } - total := len(result) - start, end := common.PagePosition(page, size, total) - return &generated.PaginatedResult{ - TotalCount: total, - HasNextPage: end < total, - Nodes: result[start:end], - Page: &page, - PageSize: &size, - }, nil + filters := make([]common.ResourceFilter, 0) + if input.DisplayName != nil { + filters = append(filters, common.FilterDatasetByDisplayName(*input.DisplayName)) + } + if input.Keyword != nil { + filters = append(filters, common.FilterDatasetByKeyword(*input.Keyword)) + } + return common.ListReources(datastList, page, size, dataset2modelConverter, filters...) } func UpdateDataset(ctx context.Context, c dynamic.Interface, input *generated.UpdateDatasetInput) (*generated.Dataset, error) { diff --git a/apiserver/pkg/datasource/datasource.go b/apiserver/pkg/datasource/datasource.go index f3cd064e3..69c5fcf71 100644 --- a/apiserver/pkg/datasource/datasource.go +++ b/apiserver/pkg/datasource/datasource.go @@ -20,8 +20,6 @@ import ( "context" "crypto/tls" "net/http" - "sort" - "strings" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" @@ -41,10 +39,14 @@ import ( "github.com/kubeagi/arcadia/pkg/utils" ) -func datasource2model(obj *unstructured.Unstructured) *generated.Datasource { +func datasource2modelConverter(obj *unstructured.Unstructured) (generated.PageNode, error) { + return datasource2model(obj) +} + +func datasource2model(obj *unstructured.Unstructured) (*generated.Datasource, error) { datasource := &v1alpha1.Datasource{} if err := utils.UnstructuredToStructured(obj, datasource); err != nil { - return &generated.Datasource{} + return nil, err } id := string(datasource.GetUID()) @@ -93,7 +95,7 @@ func datasource2model(obj *unstructured.Unstructured) *generated.Datasource { CreationTimestamp: &creationtimestamp, UpdateTimestamp: &updateTime, } - return &md + return &md, nil } func CreateDatasource(ctx context.Context, c dynamic.Interface, input generated.CreateDatasourceInput) (*generated.Datasource, error) { @@ -169,8 +171,7 @@ func CreateDatasource(ctx context.Context, c dynamic.Interface, input generated. return nil, err } } - ds := datasource2model(obj) - return ds, nil + return datasource2model(obj) } func UpdateDatasource(ctx context.Context, c dynamic.Interface, input *generated.UpdateDatasourceInput) (*generated.Datasource, error) { @@ -237,8 +238,7 @@ func UpdateDatasource(ctx context.Context, c dynamic.Interface, input *generated if err != nil { return nil, err } - ds := datasource2model(updatedObject) - return ds, nil + return datasource2model(updatedObject) } func DeleteDatasources(ctx context.Context, c dynamic.Interface, input *generated.DeleteCommonInput) (*string, error) { @@ -298,26 +298,11 @@ func ListDatasources(ctx context.Context, c dynamic.Interface, input generated.L if err != nil { return nil, err } - sort.Slice(datasList.Items, func(i, j int) bool { - return datasList.Items[i].GetCreationTimestamp().After(datasList.Items[j].GetCreationTimestamp().Time) - }) - - result := make([]generated.PageNode, 0, len(datasList.Items)) - for _, u := range datasList.Items { - m := datasource2model(&u) - // filter based on `keyword` - if keyword != "" && !strings.Contains(m.Name, keyword) && !strings.Contains(*m.DisplayName, keyword) { - continue - } - result = append(result, m) - } - totalCount := len(result) - start, end := common.PagePosition(page, pageSize, totalCount) - return &generated.PaginatedResult{ - TotalCount: totalCount, - HasNextPage: end < totalCount, - Nodes: result[start:end], - }, nil + filter := make([]common.ResourceFilter, 0) + if keyword != "" { + filter = append(filter, common.FilterDatasourceByKeyword(keyword)) + } + return common.ListReources(datasList, page, pageSize, datasource2modelConverter, filter...) } func ReadDatasource(ctx context.Context, c dynamic.Interface, name, namespace string) (*generated.Datasource, error) { @@ -330,7 +315,7 @@ func ReadDatasource(ctx context.Context, c dynamic.Interface, name, namespace st if err != nil { return nil, err } - return datasource2model(u), nil + return datasource2model(u) } // CheckDatasource diff --git a/apiserver/pkg/embedder/embedder.go b/apiserver/pkg/embedder/embedder.go index bfc00630d..5c27848f6 100644 --- a/apiserver/pkg/embedder/embedder.go +++ b/apiserver/pkg/embedder/embedder.go @@ -18,8 +18,6 @@ package embedder import ( "context" - "sort" - "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -37,11 +35,17 @@ import ( "github.com/kubeagi/arcadia/pkg/utils" ) +func embedder2modelConverter(ctx context.Context, c dynamic.Interface) func(*unstructured.Unstructured) (generated.PageNode, error) { + return func(u *unstructured.Unstructured) (generated.PageNode, error) { + return Embedder2model(ctx, c, u) + } +} + // Embedder2model convert unstructured `CR Embedder` to graphql model `Embedder` -func Embedder2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Unstructured) *generated.Embedder { +func Embedder2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Unstructured) (*generated.Embedder, error) { embedder := &v1alpha1.Embedder{} if err := utils.UnstructuredToStructured(obj, embedder); err != nil { - return &generated.Embedder{} + return nil, err } id := string(embedder.GetUID()) @@ -91,7 +95,7 @@ func Embedder2model(ctx context.Context, c dynamic.Interface, obj *unstructured. Message: &message, UpdateTimestamp: &updateTime, } - return &md + return &md, nil } func CreateEmbedder(ctx context.Context, c dynamic.Interface, input generated.CreateEmbedderInput) (*generated.Embedder, error) { @@ -174,8 +178,7 @@ func CreateEmbedder(ctx context.Context, c dynamic.Interface, input generated.Cr } } - ds := Embedder2model(ctx, c, obj) - return ds, nil + return Embedder2model(ctx, c, obj) } func UpdateEmbedder(ctx context.Context, c dynamic.Interface, input *generated.UpdateEmbedderInput) (*generated.Embedder, error) { @@ -235,8 +238,7 @@ func UpdateEmbedder(ctx context.Context, c dynamic.Interface, input *generated.U if err != nil { return nil, err } - ds := Embedder2model(ctx, c, updatedObject) - return ds, nil + return Embedder2model(ctx, c, updatedObject) } func DeleteEmbedders(ctx context.Context, c dynamic.Interface, input *generated.DeleteCommonInput) (*string, error) { @@ -304,26 +306,19 @@ func ListEmbedders(ctx context.Context, c dynamic.Interface, input generated.Lis if err != nil { return nil, err } - sort.Slice(us.Items, func(i, j int) bool { - return us.Items[i].GetCreationTimestamp().After(us.Items[j].GetCreationTimestamp().Time) - }) + filter := make([]common.ResourceFilter, 0) + if keyword != "" { + filter = append(filter, common.FilterEmbedderByKeyword(keyword)) + } + result, err := common.ListReources(us, page, pageSize, embedder2modelConverter(ctx, c), filter...) + if err != nil { + return nil, err + } - result := make([]generated.PageNode, 0, len(us.Items)) - for _, u := range us.Items { - m := Embedder2model(ctx, c, &u) - // filter based on `keyword` - if keyword != "" && !strings.Contains(m.Name, keyword) && !strings.Contains(*m.DisplayName, keyword) { - continue - } - result = append(result, opts.ConvertFunc(m)) - } - totalCount := len(result) - start, end := common.PagePosition(page, pageSize, totalCount) - return &generated.PaginatedResult{ - TotalCount: totalCount, - HasNextPage: end < totalCount, - Nodes: result[start:end], - }, nil + for i := range result.Nodes { + result.Nodes[i] = opts.ConvertFunc(result.Nodes[i]) + } + return result, nil } func ReadEmbedder(ctx context.Context, c dynamic.Interface, name, namespace string) (*generated.Embedder, error) { @@ -332,5 +327,5 @@ func ReadEmbedder(ctx context.Context, c dynamic.Interface, name, namespace stri if err != nil { return nil, err } - return Embedder2model(ctx, c, u), nil + return Embedder2model(ctx, c, u) } diff --git a/apiserver/pkg/knowledgebase/knowledgebase.go b/apiserver/pkg/knowledgebase/knowledgebase.go index 70fc06dc9..e8c7b5c35 100644 --- a/apiserver/pkg/knowledgebase/knowledgebase.go +++ b/apiserver/pkg/knowledgebase/knowledgebase.go @@ -18,8 +18,6 @@ package knowledgebase import ( "context" - "sort" - "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -34,10 +32,14 @@ import ( "github.com/kubeagi/arcadia/pkg/utils" ) -func knowledgebase2model(obj *unstructured.Unstructured) *generated.KnowledgeBase { +func knowledgebase2modelConverter(obj *unstructured.Unstructured) (generated.PageNode, error) { + return knowledgebase2model(obj) +} + +func knowledgebase2model(obj *unstructured.Unstructured) (*generated.KnowledgeBase, error) { knowledgebase := &v1alpha1.KnowledgeBase{} if err := utils.UnstructuredToStructured(obj, knowledgebase); err != nil { - return &generated.KnowledgeBase{} + return nil, err } id := string(knowledgebase.GetUID()) @@ -114,7 +116,7 @@ func knowledgebase2model(obj *unstructured.Unstructured) *generated.KnowledgeBas Reason: &reason, Message: &message, } - return &md + return &md, nil } func CreateKnowledgeBase(ctx context.Context, c dynamic.Interface, name, namespace, displayname, description, embedder string, vectorstore v1alpha1.TypedObjectReference, filegroups []v1alpha1.FileGroup) (*generated.KnowledgeBase, error) { @@ -152,7 +154,10 @@ func CreateKnowledgeBase(ctx context.Context, c dynamic.Interface, name, namespa if err != nil { return nil, err } - kb := knowledgebase2model(obj) + kb, err := knowledgebase2model(obj) + if err != nil { + return nil, err + } if kb.FileGroupDetails == nil { // fill in file group without any details details := make([]*generated.Filegroupdetail, len(filegroups)) @@ -212,7 +217,7 @@ func UpdateKnowledgeBase(ctx context.Context, c dynamic.Interface, input *genera return nil, err } - return knowledgebase2model(updatedObject), nil + return knowledgebase2model(updatedObject) } func DeleteKnowledgeBase(ctx context.Context, c dynamic.Interface, name, namespace, labelSelector, fieldSelector string) (*string, error) { @@ -240,17 +245,18 @@ func ReadKnowledgeBase(ctx context.Context, c dynamic.Interface, name, namespace if err != nil { return nil, err } - return knowledgebase2model(u), nil + return knowledgebase2model(u) } func ListKnowledgeBases(ctx context.Context, c dynamic.Interface, input generated.ListKnowledgeBaseInput) (*generated.PaginatedResult, error) { - name, displayName, labelSelector, fieldSelector := "", "", "", "" + labelSelector, fieldSelector := "", "" page, pageSize := 1, 10 + filter := make([]common.ResourceFilter, 0) if input.Name != nil { - name = *input.Name + filter = append(filter, common.FilterKnowledgeByName(*input.Name)) } if input.DisplayName != nil { - displayName = *input.DisplayName + filter = append(filter, common.FilterKnowledgeByDisplayName(*input.DisplayName)) } if input.FieldSelector != nil { fieldSelector = *input.FieldSelector @@ -274,26 +280,6 @@ func ListKnowledgeBases(ctx context.Context, c dynamic.Interface, input generate if err != nil { return nil, err } - sort.Slice(us.Items, func(i, j int) bool { - return us.Items[i].GetCreationTimestamp().After(us.Items[j].GetCreationTimestamp().Time) - }) - result := make([]*generated.KnowledgeBase, len(us.Items)) - for idx, u := range us.Items { - result[idx] = knowledgebase2model(&u) - } - - var filteredResult []generated.PageNode - for idx, u := range result { - if (name == "" || strings.Contains(u.Name, name)) && (displayName == "" || strings.Contains(*u.DisplayName, displayName)) { - filteredResult = append(filteredResult, result[idx]) - } - } - totalCount := len(filteredResult) - start, end := common.PagePosition(page, pageSize, totalCount) - return &generated.PaginatedResult{ - TotalCount: totalCount, - HasNextPage: end < totalCount, - Nodes: filteredResult[start:end], - }, nil + return common.ListReources(us, page, pageSize, knowledgebase2modelConverter, filter...) } diff --git a/apiserver/pkg/llm/llm.go b/apiserver/pkg/llm/llm.go index 2f8bc578b..542a0a51f 100644 --- a/apiserver/pkg/llm/llm.go +++ b/apiserver/pkg/llm/llm.go @@ -18,8 +18,6 @@ package llm import ( "context" - "sort" - "strings" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" @@ -36,11 +34,17 @@ import ( "github.com/kubeagi/arcadia/pkg/utils" ) +func LLM2modelConverter(ctx context.Context, c dynamic.Interface) func(*unstructured.Unstructured) (generated.PageNode, error) { + return func(u *unstructured.Unstructured) (generated.PageNode, error) { + return LLM2model(ctx, c, u) + } +} + // LLM2model convert unstructured `CR LLM` to graphql model `Llm` -func LLM2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Unstructured) *generated.Llm { +func LLM2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Unstructured) (*generated.Llm, error) { llm := &v1alpha1.LLM{} if err := utils.UnstructuredToStructured(obj, llm); err != nil { - return &generated.Llm{} + return nil, err } id := string(llm.GetUID()) @@ -94,7 +98,7 @@ func LLM2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Unstr Models: llm.GetModelList(), UpdateTimestamp: &updateTime, } - return &md + return &md, nil } // ListLLMs return a list of LLMs based on input params @@ -104,10 +108,11 @@ func ListLLMs(ctx context.Context, c dynamic.Interface, input generated.ListComm optFunc(opts) } - keyword, labelSelector, fieldSelector := "", "", "" + labelSelector, fieldSelector := "", "" page, pageSize := 1, 10 + filter := make([]common.ResourceFilter, 0) if input.Keyword != nil { - keyword = *input.Keyword + filter = append(filter, common.FilterLLMByKeyword(*input.Keyword)) } if input.FieldSelector != nil { fieldSelector = *input.FieldSelector @@ -129,26 +134,15 @@ func ListLLMs(ctx context.Context, c dynamic.Interface, input generated.ListComm if err != nil { return nil, err } + result, err := common.ListReources(us, page, pageSize, LLM2modelConverter(ctx, c), filter...) + if err != nil { + return nil, err + } - sort.Slice(us.Items, func(i, j int) bool { - return us.Items[i].GetCreationTimestamp().After(us.Items[j].GetCreationTimestamp().Time) - }) - - result := make([]generated.PageNode, 0, len(us.Items)) - for _, u := range us.Items { - m := LLM2model(ctx, c, &u) - if keyword != "" && !strings.Contains(m.Name, keyword) && !strings.Contains(*m.DisplayName, keyword) { - continue - } - result = append(result, opts.ConvertFunc(m)) - } - totalCount := len(result) - pageStart, end := common.PagePosition(page, pageSize, totalCount) - return &generated.PaginatedResult{ - TotalCount: totalCount, - HasNextPage: end < totalCount, - Nodes: result[pageStart:end], - }, nil + for i := range result.Nodes { + result.Nodes[i] = opts.ConvertFunc(result.Nodes[i]) + } + return result, nil } // ReadLLM @@ -157,7 +151,7 @@ func ReadLLM(ctx context.Context, c dynamic.Interface, name, namespace string) ( if err != nil { return nil, err } - return LLM2model(ctx, c, resource), nil + return LLM2model(ctx, c, resource) } func CreateLLM(ctx context.Context, c dynamic.Interface, input generated.CreateLLMInput) (*generated.Llm, error) { @@ -240,8 +234,7 @@ func CreateLLM(ctx context.Context, c dynamic.Interface, input generated.CreateL } // create *generated.Llm - genLLM := LLM2model(ctx, c, obj) - return genLLM, nil + return LLM2model(ctx, c, obj) } func UpdateLLM(ctx context.Context, c dynamic.Interface, input *generated.UpdateLLMInput) (*generated.Llm, error) { @@ -301,8 +294,7 @@ func UpdateLLM(ctx context.Context, c dynamic.Interface, input *generated.Update if err != nil { return nil, err } - ds := LLM2model(ctx, c, updatedObject) - return ds, nil + return LLM2model(ctx, c, updatedObject) } func DeleteLLMs(ctx context.Context, c dynamic.Interface, input *generated.DeleteCommonInput) (*string, error) { diff --git a/apiserver/pkg/model/model.go b/apiserver/pkg/model/model.go index e339ff8fa..7326a45b4 100644 --- a/apiserver/pkg/model/model.go +++ b/apiserver/pkg/model/model.go @@ -39,10 +39,13 @@ import ( "github.com/kubeagi/arcadia/pkg/utils" ) -func obj2model(obj *unstructured.Unstructured) *generated.Model { +func obj2modelConverter(obj *unstructured.Unstructured) (generated.PageNode, error) { + return obj2model(obj) +} +func obj2model(obj *unstructured.Unstructured) (*generated.Model, error) { model := &v1alpha1.Model{} if err := utils.UnstructuredToStructured(obj, model); err != nil { - return &generated.Model{} + return nil, err } id := string(model.GetUID()) @@ -75,7 +78,7 @@ func obj2model(obj *unstructured.Unstructured) *generated.Model { Status: &status, Message: &message, } - return &md + return &md, nil } func CreateModel(ctx context.Context, c dynamic.Interface, input generated.CreateModelInput) (*generated.Model, error) { @@ -117,8 +120,7 @@ func CreateModel(ctx context.Context, c dynamic.Interface, input generated.Creat if err != nil { return nil, err } - md := obj2model(obj) - return md, nil + return obj2model(obj) } func UpdateModel(ctx context.Context, c dynamic.Interface, input *generated.UpdateModelInput) (*generated.Model, error) { @@ -148,8 +150,7 @@ func UpdateModel(ctx context.Context, c dynamic.Interface, input *generated.Upda if err != nil { return nil, err } - md := obj2model(updatedObject) - return md, nil + return obj2model(updatedObject) } func DeleteModels(ctx context.Context, c dynamic.Interface, input *generated.DeleteCommonInput) (*string, error) { @@ -183,11 +184,14 @@ func DeleteModels(ctx context.Context, c dynamic.Interface, input *generated.Del } func ListModels(ctx context.Context, c dynamic.Interface, input generated.ListModelInput) (*generated.PaginatedResult, error) { - keyword, labelSelector, fieldSelector := "", "", "" + filter := make([]common.ResourceFilter, 0) + labelSelector, fieldSelector := "", "" + page, pageSize := 1, 10 if input.Keyword != nil { - keyword = *input.Keyword + filter = append(filter, common.FilterModelByKeyword(*input.Keyword)) } + if input.FieldSelector != nil { fieldSelector = *input.FieldSelector } @@ -210,40 +214,16 @@ func ListModels(ctx context.Context, c dynamic.Interface, input generated.ListMo return nil, err } - // sort by creation time - sort.Slice(models.Items, func(i, j int) bool { - return models.Items[i].GetCreationTimestamp().After(models.Items[j].GetCreationTimestamp().Time) - }) - // list models in kubeagi system namespace if input.SystemModel != nil && *input.SystemModel && input.Namespace != config.GetConfig().SystemNamespace { systemModels, err := c.Resource(common.SchemaOf(&common.ArcadiaAPIGroup, "model")).Namespace(config.GetConfig().SystemNamespace).List(ctx, listOptions) if err != nil { return nil, err } - // sort by creation time - sort.Slice(systemModels.Items, func(i, j int) bool { - return systemModels.Items[i].GetCreationTimestamp().After(systemModels.Items[j].GetCreationTimestamp().Time) - }) models.Items = append(systemModels.Items, models.Items...) } - result := make([]generated.PageNode, 0, len(models.Items)) - for _, u := range models.Items { - m := obj2model(&u) - // filter based on `keyword` - if keyword != "" && !strings.Contains(m.Name, keyword) && !strings.Contains(*m.DisplayName, keyword) { - continue - } - result = append(result, m) - } - totalCount := len(result) - pageStart, end := common.PagePosition(page, pageSize, totalCount) - return &generated.PaginatedResult{ - TotalCount: totalCount, - HasNextPage: end < totalCount, - Nodes: result[pageStart:end], - }, nil + return common.ListReources(models, page, pageSize, obj2modelConverter, filter...) } func ReadModel(ctx context.Context, c dynamic.Interface, name, namespace string) (*generated.Model, error) { @@ -252,7 +232,7 @@ func ReadModel(ctx context.Context, c dynamic.Interface, name, namespace string) if err != nil { return nil, err } - return obj2model(u), nil + return obj2model(u) } func ModelFiles(ctx context.Context, c dynamic.Interface, modelName, namespace string, input *generated.FileFilter) (*generated.PaginatedResult, error) { diff --git a/apiserver/pkg/versioneddataset/versioned_dataset.go b/apiserver/pkg/versioneddataset/versioned_dataset.go index 66fcfd691..b544cd556 100644 --- a/apiserver/pkg/versioneddataset/versioned_dataset.go +++ b/apiserver/pkg/versioneddataset/versioned_dataset.go @@ -47,6 +47,10 @@ var ( } ) +func versionedDataset2modelConverter(obj *unstructured.Unstructured) (generated.PageNode, error) { + return versionedDataset2model(obj) +} + func versionedDataset2model(obj *unstructured.Unstructured) (*generated.VersionedDataset, error) { vds := &generated.VersionedDataset{} id := string(obj.GetUID()) @@ -196,19 +200,6 @@ func ListVersionedDatasets(ctx context.Context, c dynamic.Interface, input *gene listOptions.FieldSelector = *input.FieldSelector } } - ns := "default" - if input.Namespace != nil { - ns = *input.Namespace - } - list, err := c.Resource(versioneddatasetSchem).Namespace(ns).List(ctx, listOptions) - if err != nil { - return nil, err - } - - sort.Slice(list.Items, func(i, j int) bool { - return list.Items[i].GetCreationTimestamp().After(list.Items[j].GetCreationTimestamp().Time) - }) - page, size := 1, 10 if input.Page != nil && *input.Page > 0 { page = *input.Page @@ -216,41 +207,22 @@ func ListVersionedDatasets(ctx context.Context, c dynamic.Interface, input *gene if input.PageSize != nil && *input.PageSize > 0 { size = *input.PageSize } - result := make([]generated.PageNode, 0) - for _, u := range list.Items { - uu, _ := versionedDataset2model(&u) - if input.DisplayName != nil && *uu.DisplayName != *input.DisplayName { - continue - } - if input.Keyword != nil { - if strings.Contains(uu.Name, *input.Keyword) { - goto add - } - if strings.Contains(uu.Namespace, *input.Keyword) { - goto add - } - if strings.Contains(*uu.DisplayName, *input.Keyword) { - goto add - } - for _, v := range uu.Annotations { - if strings.Contains(v.(string), *input.Keyword) { - goto add - } - } - continue - } - add: - result = append(result, uu) + ns := "default" + if input.Namespace != nil { + ns = *input.Namespace } - total := len(result) - start, end := common.PagePosition(page, size, total) - return &generated.PaginatedResult{ - TotalCount: total, - HasNextPage: end < total, - Nodes: result[start:end], - Page: &page, - PageSize: &size, - }, nil + filter := make([]common.ResourceFilter, 0) + if input.DisplayName != nil { + filter = append(filter, common.FilterVersionedDatasetByDisplayName(*input.DisplayName)) + } + if input.Keyword != nil { + filter = append(filter, common.FilterVersionedDatasetByKeyword(*input.Keyword)) + } + list, err := c.Resource(versioneddatasetSchem).Namespace(ns).List(ctx, listOptions) + if err != nil { + return nil, err + } + return common.ListReources(list, page, size, versionedDataset2modelConverter, filter...) } func GetVersionedDataset(ctx context.Context, c dynamic.Interface, name, namespace string) (*generated.VersionedDataset, error) { diff --git a/apiserver/pkg/worker/worker.go b/apiserver/pkg/worker/worker.go index 763103e2e..b23776764 100644 --- a/apiserver/pkg/worker/worker.go +++ b/apiserver/pkg/worker/worker.go @@ -19,9 +19,7 @@ package worker import ( "context" "fmt" - "sort" "strconv" - "strings" "github.com/pkg/errors" v1 "k8s.io/api/core/v1" @@ -45,11 +43,17 @@ const ( NvidiaGPU = "nvidia.com/gpu" ) +func worker2modelConverter(ctx context.Context, c dynamic.Interface) func(*unstructured.Unstructured) (generated.PageNode, error) { + return func(u *unstructured.Unstructured) (generated.PageNode, error) { + return Worker2model(ctx, c, u) + } +} + // Worker2model convert unstructured `CR Worker` to graphql model -func Worker2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Unstructured) *generated.Worker { +func Worker2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Unstructured) (*generated.Worker, error) { worker := &v1alpha1.Worker{} if err := utils.UnstructuredToStructured(obj, worker); err != nil { - return &generated.Worker{} + return nil, err } id := string(worker.GetUID()) @@ -145,7 +149,7 @@ func Worker2model(ctx context.Context, c dynamic.Interface, obj *unstructured.Un } } - return &w + return &w, nil } func CreateWorker(ctx context.Context, c dynamic.Interface, input generated.CreateWorkerInput) (*generated.Worker, error) { @@ -243,7 +247,7 @@ func CreateWorker(ctx context.Context, c dynamic.Interface, input generated.Crea if err != nil { return nil, err } - return Worker2model(ctx, c, obj), nil + return Worker2model(ctx, c, obj) } func UpdateWorker(ctx context.Context, c dynamic.Interface, input *generated.UpdateWorkerInput) (*generated.Worker, error) { @@ -341,7 +345,7 @@ func UpdateWorker(ctx context.Context, c dynamic.Interface, input *generated.Upd return nil, err } - return Worker2model(ctx, c, updatedObject), nil + return Worker2model(ctx, c, updatedObject) } func DeleteWorkers(ctx context.Context, c dynamic.Interface, input *generated.DeleteCommonInput) (*string, error) { @@ -381,13 +385,14 @@ func ListWorkers(ctx context.Context, c dynamic.Interface, input generated.ListW optFunc(opts) } - keyword, modelTypes, labelSelector, fieldSelector := "", "", "", "" + filter := make([]common.ResourceFilter, 0) + labelSelector, fieldSelector := "", "" page, pageSize := 1, 10 if input.Keyword != nil { - keyword = *input.Keyword + filter = append(filter, common.FilterWorkerByKeyword(*input.Keyword)) } if input.ModelTypes != nil { - modelTypes = *input.ModelTypes + filter = append(filter, common.FilterWorkerByType(c, input.Namespace, *input.ModelTypes)) } if input.FieldSelector != nil { fieldSelector = *input.FieldSelector @@ -410,31 +415,7 @@ func ListWorkers(ctx context.Context, c dynamic.Interface, input generated.ListW if err != nil { return nil, err } - // sort by creation time - sort.Slice(us.Items, func(i, j int) bool { - return us.Items[i].GetCreationTimestamp().After(us.Items[j].GetCreationTimestamp().Time) - }) - - result := make([]generated.PageNode, 0, len(us.Items)) - for _, u := range us.Items { - m := Worker2model(ctx, c, &u) - // filter based on `keyword` - if keyword != "" && !strings.Contains(m.Name, keyword) && !strings.Contains(*m.DisplayName, keyword) { - continue - } - // filter based on `modelTypes` - if modelTypes != "" && !strings.Contains(m.ModelTypes, modelTypes) { - continue - } - result = append(result, opts.ConvertFunc(m)) - } - totalCount := len(result) - start, end := common.PagePosition(page, pageSize, totalCount) - return &generated.PaginatedResult{ - TotalCount: totalCount, - HasNextPage: end < totalCount, - Nodes: result[start:end], - }, nil + return common.ListReources(us, page, pageSize, worker2modelConverter(ctx, c), filter...) } func ReadWorker(ctx context.Context, c dynamic.Interface, name, namespace string) (*generated.Worker, error) { @@ -447,5 +428,5 @@ func ReadWorker(ctx context.Context, c dynamic.Interface, name, namespace string if err != nil { return nil, err } - return Worker2model(ctx, c, u), nil + return Worker2model(ctx, c, u) }