diff --git a/pkg/apimachinery/apis/common/v0alpha1/resource.go b/pkg/apimachinery/apis/common/v0alpha1/resource.go index b801ed69e9e..6ccebac6175 100644 --- a/pkg/apimachinery/apis/common/v0alpha1/resource.go +++ b/pkg/apimachinery/apis/common/v0alpha1/resource.go @@ -1,6 +1,7 @@ package v0alpha1 import ( + "github.com/grafana/grafana/pkg/apimachinery/utils" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -18,12 +19,13 @@ type ResourceInfo struct { kind string newObj func() runtime.Object newList func() runtime.Object + columns utils.TableColumns } func NewResourceInfo(group, version, resourceName, singularName, kind string, - newObj func() runtime.Object, newList func() runtime.Object) ResourceInfo { + newObj func() runtime.Object, newList func() runtime.Object, columns utils.TableColumns) ResourceInfo { shortName := "" // an optional alias helpful in kubectl eg ("sa" for serviceaccounts) - return ResourceInfo{group, version, resourceName, singularName, shortName, kind, newObj, newList} + return ResourceInfo{group, version, resourceName, singularName, shortName, kind, newObj, newList, columns} } func (info *ResourceInfo) WithGroupAndShortName(group string, shortName string) ResourceInfo { @@ -36,6 +38,7 @@ func (info *ResourceInfo) WithGroupAndShortName(group string, shortName string) shortName: shortName, newObj: info.newObj, newList: info.newList, + columns: info.columns, } } @@ -113,6 +116,10 @@ func (info *ResourceInfo) NewListFunc() runtime.Object { return info.newList() } +func (info *ResourceInfo) TableConverter() utils.TableConvertor { + return utils.NewTableConverter(info.GroupResource(), info.columns) +} + func (info *ResourceInfo) NewNotFound(name string) *errors.StatusError { return errors.NewNotFound(info.SingularGroupResource(), name) } diff --git a/pkg/apimachinery/apis/identity/v0alpha1/register.go b/pkg/apimachinery/apis/identity/v0alpha1/register.go index f8aafa213b4..18ad5cc0677 100644 --- a/pkg/apimachinery/apis/identity/v0alpha1/register.go +++ b/pkg/apimachinery/apis/identity/v0alpha1/register.go @@ -1,10 +1,14 @@ package v0alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + "fmt" + "time" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -17,18 +21,78 @@ var UserResourceInfo = common.NewResourceInfo(GROUP, VERSION, "users", "user", "User", func() runtime.Object { return &User{} }, func() runtime.Object { return &UserList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Login", Type: "string", Format: "string", Description: "The user login"}, + {Name: "Email", Type: "string", Format: "string", Description: "The user email"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + u, ok := obj.(*User) + if ok { + return []interface{}{ + u.Name, + u.Spec.Login, + u.Spec.Email, + u.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + } + return nil, fmt.Errorf("expected user") + }, + }, ) var TeamResourceInfo = common.NewResourceInfo(GROUP, VERSION, "teams", "team", "Team", func() runtime.Object { return &Team{} }, func() runtime.Object { return &TeamList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The team name"}, + {Name: "Email", Type: "string", Format: "string", Description: "team email"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*Team) + if !ok { + return nil, fmt.Errorf("expected team") + } + return []interface{}{ + m.Name, + m.Spec.Title, + m.Spec.Email, + m.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + }, + }, ) var ServiceAccountResourceInfo = common.NewResourceInfo(GROUP, VERSION, "serviceaccounts", "serviceaccount", "ServiceAccount", func() runtime.Object { return &ServiceAccount{} }, func() runtime.Object { return &ServiceAccountList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Account", Type: "string", Format: "string", Description: "The service account email"}, + {Name: "Email", Type: "string", Format: "string", Description: "The user email"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + u, ok := obj.(*ServiceAccount) + if ok { + return []interface{}{ + u.Name, + u.Spec.Name, + u.Spec.Email, + u.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + } + return nil, fmt.Errorf("expected service account") + }, + }, ) var ( diff --git a/pkg/services/apiserver/utils/tableConverter.go b/pkg/apimachinery/utils/tableConverter.go similarity index 61% rename from pkg/services/apiserver/utils/tableConverter.go rename to pkg/apimachinery/utils/tableConverter.go index 755dc8f74f4..826353d9a15 100644 --- a/pkg/services/apiserver/utils/tableConverter.go +++ b/pkg/apimachinery/utils/tableConverter.go @@ -11,70 +11,74 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apiserver/pkg/endpoints/request" - "k8s.io/apiserver/pkg/registry/rest" ) +type TableColumns struct { + Definition []metav1.TableColumnDefinition + Reader func(obj any) ([]interface{}, error) +} + +type TableConvertor interface { + ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) +} + // Based on https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/registry/rest/table.go type customTableConvertor struct { gr schema.GroupResource - columns []metav1.TableColumnDefinition - reader func(obj any) ([]interface{}, error) + columns TableColumns } -func NewTableConverter(gr schema.GroupResource, columns []metav1.TableColumnDefinition, reader func(obj any) ([]interface{}, error)) rest.TableConvertor { - converter := customTableConvertor{ - gr: gr, - columns: columns, - reader: reader, +func NewTableConverter(gr schema.GroupResource, columns TableColumns) TableConvertor { + if columns.Reader == nil { + columns = TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + v, err := meta.Accessor(obj) + if err == nil && v != nil { + return []interface{}{ + v.GetName(), + v.GetCreationTimestamp().UTC().Format(time.RFC3339), + }, nil + } + + r := reflect.ValueOf(obj).Elem() + n := r.FieldByName("Name").String() + if n != "" { + return []interface{}{ + n, + "", + }, nil + } + + return []interface{}{ + fmt.Sprintf("%v", obj), + "", + }, nil + }, + } } + // Replace the description on standard columns with the global values - for idx, column := range converter.columns { + for idx, column := range columns.Definition { if column.Description == "" { switch column.Name { case "Name": - converter.columns[idx].Description = swaggerMetadataDescriptions["name"] + columns.Definition[idx].Description = swaggerMetadataDescriptions["name"] case "Created At": - converter.columns[idx].Description = swaggerMetadataDescriptions["creationTimestamp"] + columns.Definition[idx].Description = swaggerMetadataDescriptions["creationTimestamp"] } } } - return converter + return customTableConvertor{ + gr: gr, + columns: columns, + } } -func NewDefaultTableConverter(gr schema.GroupResource) rest.TableConvertor { - return NewTableConverter(gr, - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - v, err := meta.Accessor(obj) - if err == nil && v != nil { - return []interface{}{ - v.GetName(), - v.GetCreationTimestamp().UTC().Format(time.RFC3339), - }, nil - } - - r := reflect.ValueOf(obj).Elem() - n := r.FieldByName("Name").String() - if n != "" { - return []interface{}{ - n, - "", - }, nil - } - - return []interface{}{ - fmt.Sprintf("%v", obj), - "", - }, nil - }, - ) -} - -var _ rest.TableConvertor = &customTableConvertor{} +var _ TableConvertor = &customTableConvertor{} var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() func (c customTableConvertor) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { @@ -85,12 +89,9 @@ func (c customTableConvertor) ConvertToTable(ctx context.Context, object runtime table = &metav1.Table{} } fn := func(obj runtime.Object) error { - cells, err := c.reader(obj) + cells, err := c.columns.Reader(obj) if err != nil { resource := c.gr - if info, ok := request.RequestInfoFrom(ctx); ok { - resource = schema.GroupResource{Group: info.APIGroup, Resource: info.Resource} - } return errNotAcceptable{resource: resource} } table.Rows = append(table.Rows, metav1.TableRow{ @@ -119,7 +120,7 @@ func (c customTableConvertor) ConvertToTable(ctx context.Context, object runtime } } if opt, ok := tableOptions.(*metav1.TableOptions); !ok || !opt.NoHeaders { - table.ColumnDefinitions = c.columns + table.ColumnDefinitions = c.columns.Definition } return table, nil } diff --git a/pkg/services/apiserver/utils/tableConverter_test.go b/pkg/apimachinery/utils/tableConverter_test.go similarity index 79% rename from pkg/services/apiserver/utils/tableConverter_test.go rename to pkg/apimachinery/utils/tableConverter_test.go index 7c7618d4f0d..d3bb813e201 100644 --- a/pkg/services/apiserver/utils/tableConverter_test.go +++ b/pkg/apimachinery/utils/tableConverter_test.go @@ -7,33 +7,34 @@ import ( "testing" "time" + "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/stretchr/testify/require" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime/schema" - - "github.com/grafana/grafana/pkg/services/apiserver/utils" ) func TestTableConverter(t *testing.T) { // dummy converter converter := utils.NewTableConverter( schema.GroupResource{Group: "x", Resource: "y"}, - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Dummy", Type: "string", Format: "string", Description: "Something here"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*metav1.APIGroup) - if !ok { - return nil, fmt.Errorf("expected status") - } - ts := metav1.NewTime(time.UnixMilli(10000000)) - return []interface{}{ - m.Name, - "dummy", - ts.Time.UTC().Format(time.RFC3339), - }, nil + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Dummy", Type: "string", Format: "string", Description: "Something here"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*metav1.APIGroup) + if !ok { + return nil, fmt.Errorf("expected status") + } + ts := metav1.NewTime(time.UnixMilli(10000000)) + return []interface{}{ + m.Name, + "dummy", + ts.Time.UTC().Format(time.RFC3339), + }, nil + }, }, ) @@ -97,7 +98,7 @@ func TestTableConverter(t *testing.T) { // Default table converter // Convert a single table - converter = utils.NewDefaultTableConverter(schema.GroupResource{Group: "x", Resource: "y"}) + converter = utils.NewTableConverter(schema.GroupResource{Group: "x", Resource: "y"}, utils.TableColumns{}) table, err = converter.ConvertToTable(context.Background(), &metav1.APIGroup{ Name: "hello", }, nil) diff --git a/pkg/apis/alerting_notifications/v0alpha1/register.go b/pkg/apis/alerting_notifications/v0alpha1/register.go index c773957155c..b18547aa181 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/register.go +++ b/pkg/apis/alerting_notifications/v0alpha1/register.go @@ -10,6 +10,7 @@ import ( "k8s.io/apiserver/pkg/registry/generic" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1" ) @@ -28,11 +29,44 @@ var ( "timeintervals", "timeinterval", "TimeInterval", func() runtime.Object { return &TimeInterval{} }, func() runtime.Object { return &TimeIntervalList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + // {Name: "Intervals", Type: "string", Format: "string", Description: "The display name"}, + }, + Reader: func(obj any) ([]interface{}, error) { + r, ok := obj.(*TimeInterval) + if ok { + return []interface{}{ + r.Name, + // r.Spec, //TODO implement formatting for Spec, same as UI? + }, nil + } + return nil, fmt.Errorf("expected resource or info") + }, + }, ) ReceiverResourceInfo = common.NewResourceInfo(GROUP, VERSION, "receivers", "receiver", "Receiver", func() runtime.Object { return &Receiver{} }, func() runtime.Object { return &ReceiverList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The receiver name"}, // TODO: Add integration types. + }, + Reader: func(obj any) ([]interface{}, error) { + r, ok := obj.(*Receiver) + if ok { + return []interface{}{ + r.Name, + r.Spec.Title, + // r.Spec, //TODO implement formatting for Spec, same as UI? + }, nil + } + return nil, fmt.Errorf("expected resource or info") + }, + }, ) // SchemeGroupVersion is group version used to register these objects SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION} diff --git a/pkg/apis/dashboard/v0alpha1/register.go b/pkg/apis/dashboard/v0alpha1/register.go index 2b8a97f1eb5..103f9e579db 100644 --- a/pkg/apis/dashboard/v0alpha1/register.go +++ b/pkg/apis/dashboard/v0alpha1/register.go @@ -1,10 +1,14 @@ package v0alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + "fmt" + "time" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -17,6 +21,26 @@ var DashboardResourceInfo = common.NewResourceInfo(GROUP, VERSION, "dashboards", "dashboard", "Dashboard", func() runtime.Object { return &Dashboard{} }, func() runtime.Object { return &DashboardList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The dashboard name"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + dash, ok := obj.(*Dashboard) + if ok { + if dash != nil { + return []interface{}{ + dash.Name, + dash.Spec.GetNestedString("title"), + dash.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + } + } + return nil, fmt.Errorf("expected dashboard or summary") + }, + }, ) var ( diff --git a/pkg/apis/dashboardsnapshot/v0alpha1/register.go b/pkg/apis/dashboardsnapshot/v0alpha1/register.go index 57209ad7b91..113bc031fc0 100644 --- a/pkg/apis/dashboardsnapshot/v0alpha1/register.go +++ b/pkg/apis/dashboardsnapshot/v0alpha1/register.go @@ -1,10 +1,14 @@ package v0alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + "fmt" + "time" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -17,6 +21,24 @@ var DashboardSnapshotResourceInfo = common.NewResourceInfo(GROUP, VERSION, "dashboardsnapshots", "dashboardsnapshot", "DashboardSnapshot", func() runtime.Object { return &DashboardSnapshot{} }, func() runtime.Object { return &DashboardSnapshotList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The snapshot name"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*DashboardSnapshot) + if ok { + return []interface{}{ + m.Name, + m.Spec.Title, + m.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + } + return nil, fmt.Errorf("expected snapshot") + }, + }, // default table converter ) var ( diff --git a/pkg/apis/datasource/v0alpha1/register.go b/pkg/apis/datasource/v0alpha1/register.go index 7eae1938ef7..526f48c8a4d 100644 --- a/pkg/apis/datasource/v0alpha1/register.go +++ b/pkg/apis/datasource/v0alpha1/register.go @@ -1,9 +1,13 @@ package v0alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" + "fmt" + "time" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" ) const ( @@ -15,4 +19,24 @@ var GenericConnectionResourceInfo = common.NewResourceInfo(GROUP, VERSION, "connections", "connection", "DataSourceConnection", func() runtime.Object { return &DataSourceConnection{} }, func() runtime.Object { return &DataSourceConnectionList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The datasource title"}, + {Name: "APIVersion", Type: "string", Format: "string", Description: "API Version"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*DataSourceConnection) + if !ok { + return nil, fmt.Errorf("expected connection") + } + return []interface{}{ + m.Name, + m.Title, + m.APIVersion, + m.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + }, + }, ) diff --git a/pkg/apis/featuretoggle/v0alpha1/register.go b/pkg/apis/featuretoggle/v0alpha1/register.go index b1e7d062387..b3b6129c471 100644 --- a/pkg/apis/featuretoggle/v0alpha1/register.go +++ b/pkg/apis/featuretoggle/v0alpha1/register.go @@ -1,10 +1,13 @@ package v0alpha1 import ( - runtime "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + "fmt" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -18,6 +21,24 @@ var FeatureResourceInfo = common.NewResourceInfo(GROUP, VERSION, "features", "feature", "Feature", func() runtime.Object { return &Feature{} }, func() runtime.Object { return &FeatureList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Stage", Type: "string", Format: "string", Description: "Where is the flag in the dev cycle"}, + {Name: "Owner", Type: "string", Format: "string", Description: "Which team owns the feature"}, + }, + Reader: func(obj any) ([]interface{}, error) { + r, ok := obj.(*Feature) + if ok { + return []interface{}{ + r.Name, + r.Spec.Stage, + r.Spec.Owner, + }, nil + } + return nil, fmt.Errorf("expected resource or info") + }, + }, ) // TogglesResourceInfo represents the actual configuration @@ -25,6 +46,7 @@ var TogglesResourceInfo = common.NewResourceInfo(GROUP, VERSION, "featuretoggles", "featuretoggle", "FeatureToggles", func() runtime.Object { return &FeatureToggles{} }, func() runtime.Object { return &FeatureTogglesList{} }, + utils.TableColumns{}, // default table converter ) var ( diff --git a/pkg/apis/folder/v0alpha1/register.go b/pkg/apis/folder/v0alpha1/register.go index 010054ad15c..a56362276b9 100644 --- a/pkg/apis/folder/v0alpha1/register.go +++ b/pkg/apis/folder/v0alpha1/register.go @@ -1,10 +1,13 @@ package v0alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + "fmt" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -18,6 +21,25 @@ var FolderResourceInfo = common.NewResourceInfo(GROUP, VERSION, RESOURCE, "folder", "Folder", func() runtime.Object { return &Folder{} }, func() runtime.Object { return &FolderList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The display name"}, + {Name: "Parent", Type: "string", Format: "string", Description: "Parent folder UID"}, + }, + Reader: func(obj any) ([]interface{}, error) { + r, ok := obj.(*Folder) + if ok { + accessor, _ := utils.MetaAccessor(r) + return []interface{}{ + r.Name, + r.Spec.Title, + accessor.GetFolder(), + }, nil + } + return nil, fmt.Errorf("expected folder") + }, + }, ) var ( diff --git a/pkg/apis/peakq/v0alpha1/register.go b/pkg/apis/peakq/v0alpha1/register.go index b58288d3538..fdbc6689d73 100644 --- a/pkg/apis/peakq/v0alpha1/register.go +++ b/pkg/apis/peakq/v0alpha1/register.go @@ -6,6 +6,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" ) const ( @@ -18,6 +19,7 @@ var QueryTemplateResourceInfo = common.NewResourceInfo(GROUP, VERSION, "querytemplates", "querytemplate", "QueryTemplate", func() runtime.Object { return &QueryTemplate{} }, func() runtime.Object { return &QueryTemplateList{} }, + utils.TableColumns{}, // default table converter ) var ( diff --git a/pkg/apis/playlist/v0alpha1/register.go b/pkg/apis/playlist/v0alpha1/register.go index 0b10256f759..ce5dddbee6f 100644 --- a/pkg/apis/playlist/v0alpha1/register.go +++ b/pkg/apis/playlist/v0alpha1/register.go @@ -1,10 +1,14 @@ package v0alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + "fmt" + "time" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -19,6 +23,26 @@ var PlaylistResourceInfo = common.NewResourceInfo(GROUP, VERSION, RESOURCE, "playlist", "Playlist", func() runtime.Object { return &Playlist{} }, func() runtime.Object { return &PlaylistList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The playlist name"}, + {Name: "Interval", Type: "string", Format: "string", Description: "How often the playlist will update"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*Playlist) + if !ok { + return nil, fmt.Errorf("expected playlist") + } + return []interface{}{ + m.Name, + m.Spec.Title, + m.Spec.Interval, + m.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + }, + }, ) var ( diff --git a/pkg/apis/query/v0alpha1/register.go b/pkg/apis/query/v0alpha1/register.go index c62fc559eb7..faabe486aca 100644 --- a/pkg/apis/query/v0alpha1/register.go +++ b/pkg/apis/query/v0alpha1/register.go @@ -5,6 +5,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" ) const ( @@ -17,12 +18,14 @@ var DataSourceApiServerResourceInfo = common.NewResourceInfo(GROUP, VERSION, "datasourceapiservers", "datasourceapiserver", "DataSourceApiServer", func() runtime.Object { return &DataSourceApiServer{} }, func() runtime.Object { return &DataSourceApiServerList{} }, + utils.TableColumns{}, // default table converter ) var QueryTypeDefinitionResourceInfo = common.NewResourceInfo(GROUP, VERSION, "querytypes", "querytype", "QueryTypeDefinition", func() runtime.Object { return &QueryTypeDefinition{} }, func() runtime.Object { return &QueryTypeDefinitionList{} }, + utils.TableColumns{}, // default table converter ) var ( diff --git a/pkg/apis/scope/v0alpha1/register.go b/pkg/apis/scope/v0alpha1/register.go index 6c7cdf434f1..7d301cba8a9 100644 --- a/pkg/apis/scope/v0alpha1/register.go +++ b/pkg/apis/scope/v0alpha1/register.go @@ -1,10 +1,14 @@ package v0alpha1 import ( - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/apimachinery/pkg/runtime/schema" + "fmt" + "time" common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" ) const ( @@ -17,18 +21,84 @@ var ScopeResourceInfo = common.NewResourceInfo(GROUP, VERSION, "scopes", "scope", "Scope", func() runtime.Object { return &Scope{} }, func() runtime.Object { return &ScopeList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + {Name: "Title", Type: "string"}, + {Name: "Filters", Type: "array"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*Scope) + if !ok { + return nil, fmt.Errorf("expected scope") + } + return []interface{}{ + m.Name, + m.CreationTimestamp.UTC().Format(time.RFC3339), + m.Spec.Title, + m.Spec.Filters, + }, nil + }, + }, // default table converter ) var ScopeDashboardBindingResourceInfo = common.NewResourceInfo(GROUP, VERSION, "scopedashboardbindings", "scopedashboardbinding", "ScopeDashboardBinding", func() runtime.Object { return &ScopeDashboardBinding{} }, func() runtime.Object { return &ScopeDashboardBindingList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + {Name: "Dashboard", Type: "string"}, + {Name: "Scope", Type: "string"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*ScopeDashboardBinding) + if !ok { + return nil, fmt.Errorf("expected scope dashboard binding") + } + return []interface{}{ + m.Name, + m.CreationTimestamp.UTC().Format(time.RFC3339), + m.Spec.Dashboard, + m.Spec.Scope, + }, nil + }, + }, ) var ScopeNodeResourceInfo = common.NewResourceInfo(GROUP, VERSION, "scopenodes", "scopenode", "ScopeNode", func() runtime.Object { return &ScopeNode{} }, func() runtime.Object { return &ScopeNodeList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Created At", Type: "date"}, + {Name: "Title", Type: "string"}, + {Name: "Parent Name", Type: "string"}, + {Name: "Node Type", Type: "string"}, + {Name: "Link Type", Type: "string"}, + {Name: "Link ID", Type: "string"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*ScopeNode) + if !ok { + return nil, fmt.Errorf("expected scope node") + } + return []interface{}{ + m.Name, + m.CreationTimestamp.UTC().Format(time.RFC3339), + m.Spec.Title, + m.Spec.ParentName, + m.Spec.NodeType, + m.Spec.LinkType, + m.Spec.LinkID, + }, nil + }, + }, // default table converter ) var ( diff --git a/pkg/apis/service/v0alpha1/register.go b/pkg/apis/service/v0alpha1/register.go index 530b1914feb..9cc423d51ab 100644 --- a/pkg/apis/service/v0alpha1/register.go +++ b/pkg/apis/service/v0alpha1/register.go @@ -1,11 +1,14 @@ package v0alpha1 import ( + "fmt" + "time" + + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" + "github.com/grafana/grafana/pkg/apimachinery/utils" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - - common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" ) const ( @@ -18,6 +21,24 @@ var ExternalNameResourceInfo = common.NewResourceInfo(GROUP, VERSION, "externalnames", "externalname", "ExternalName", func() runtime.Object { return &ExternalName{} }, func() runtime.Object { return &ExternalNameList{} }, + utils.TableColumns{ + Definition: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Host", Type: "string", Format: "string", Description: "The service host"}, + {Name: "Created At", Type: "date"}, + }, + Reader: func(obj any) ([]interface{}, error) { + m, ok := obj.(*ExternalName) + if !ok { + return nil, fmt.Errorf("expected external name") + } + return []interface{}{ + m.Name, + m.Spec.Host, + m.CreationTimestamp.UTC().Format(time.RFC3339), + }, nil + }, + }, // default table converter ) var ( diff --git a/pkg/registry/apis/alerting/notifications/receiver/storage.go b/pkg/registry/apis/alerting/notifications/receiver/storage.go index 7563fa7060e..640292006e9 100644 --- a/pkg/registry/apis/alerting/notifications/receiver/storage.go +++ b/pkg/registry/apis/alerting/notifications/receiver/storage.go @@ -1,19 +1,14 @@ package receiver import ( - "fmt" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "k8s.io/apiserver/pkg/registry/rest" - model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/apiserver/utils" ) var _ grafanarest.Storage = (*storage)(nil) @@ -35,25 +30,9 @@ func NewStorage( dualWriteBuilder grafanarest.DualWriteBuilder, ) (rest.Storage, error) { legacyStore := &legacyStorage{ - service: legacySvc, - namespacer: namespacer, - tableConverter: utils.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The receiver name"}, // TODO: Add integration types. - }, - func(obj any) ([]interface{}, error) { - r, ok := obj.(*model.Receiver) - if ok { - return []interface{}{ - r.Name, - r.Spec.Title, - // r.Spec, //TODO implement formatting for Spec, same as UI? - }, nil - } - return nil, fmt.Errorf("expected resource or info") - }), + service: legacySvc, + namespacer: namespacer, + tableConverter: resourceInfo.TableConverter(), } if optsGetter != nil && dualWriteBuilder != nil { strategy := grafanaregistry.NewStrategy(scheme) diff --git a/pkg/registry/apis/alerting/notifications/timeinterval/storage.go b/pkg/registry/apis/alerting/notifications/timeinterval/storage.go index 38b9123a39b..8dfd8bf1b6f 100644 --- a/pkg/registry/apis/alerting/notifications/timeinterval/storage.go +++ b/pkg/registry/apis/alerting/notifications/timeinterval/storage.go @@ -3,7 +3,6 @@ package timeinterval import ( "fmt" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -16,7 +15,6 @@ import ( grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - "github.com/grafana/grafana/pkg/services/apiserver/utils" ) var _ grafanarest.Storage = (*storage)(nil) @@ -38,24 +36,9 @@ func NewStorage( dualWriteBuilder grafanarest.DualWriteBuilder, ) (rest.Storage, error) { legacyStore := &legacyStorage{ - service: legacySvc, - namespacer: namespacer, - tableConverter: utils.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - // {Name: "Intervals", Type: "string", Format: "string", Description: "The display name"}, - }, - func(obj any) ([]interface{}, error) { - r, ok := obj.(*model.TimeInterval) - if ok { - return []interface{}{ - r.Name, - // r.Spec, //TODO implement formatting for Spec, same as UI? - }, nil - } - return nil, fmt.Errorf("expected resource or info") - }), + service: legacySvc, + namespacer: namespacer, + tableConverter: resourceInfo.TableConverter(), } if optsGetter != nil && dualWriteBuilder != nil { strategy := grafanaregistry.NewStrategy(scheme) diff --git a/pkg/registry/apis/dashboard/register.go b/pkg/registry/apis/dashboard/register.go index 2e583d8588a..f693b42f346 100644 --- a/pkg/registry/apis/dashboard/register.go +++ b/pkg/registry/apis/dashboard/register.go @@ -1,9 +1,6 @@ package dashboard import ( - "fmt" - "time" - "github.com/prometheus/client_golang/prometheus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -25,7 +22,6 @@ import ( "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/provisioning" @@ -67,28 +63,9 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, accessControl: accessControl, legacy: &dashboardStorage{ - resource: dashboard.DashboardResourceInfo, - access: legacy.NewDashboardAccess(sql, namespacer, dashStore, provisioning), - tableConverter: gapiutil.NewTableConverter( - dashboard.DashboardResourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The dashboard name"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - dash, ok := obj.(*dashboard.Dashboard) - if ok { - if dash != nil { - return []interface{}{ - dash.Name, - dash.Spec.GetNestedString("title"), - dash.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - } - } - return nil, fmt.Errorf("expected dashboard or summary") - }), + resource: dashboard.DashboardResourceInfo, + access: legacy.NewDashboardAccess(sql, namespacer, dashStore, provisioning), + tableConverter: dashboard.DashboardResourceInfo.TableConverter(), }, } apiregistration.RegisterAPI(builder) diff --git a/pkg/registry/apis/dashboard/storage.go b/pkg/registry/apis/dashboard/storage.go index ace79bc02e3..8995513eb6f 100644 --- a/pkg/registry/apis/dashboard/storage.go +++ b/pkg/registry/apis/dashboard/storage.go @@ -1,17 +1,12 @@ package dashboard import ( - "fmt" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" ) var _ grafanarest.Storage = (*storage)(nil) @@ -34,27 +29,8 @@ func newStorage(scheme *runtime.Scheme) (*storage, error) { CreateStrategy: strategy, UpdateStrategy: strategy, DeleteStrategy: strategy, + TableConvertor: resourceInfo.TableConverter(), } - store.TableConvertor = gapiutil.NewTableConverter( - store.DefaultQualifiedResource, - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The dashboard name"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - dash, ok := obj.(*v0alpha1.Dashboard) - if ok { - if dash != nil { - return []interface{}{ - dash.Name, - dash.Spec.GetNestedString("title"), - dash.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - } - } - return nil, fmt.Errorf("expected dashboard or summary") - }) return &storage{Store: store}, nil } diff --git a/pkg/registry/apis/dashboardsnapshot/register.go b/pkg/registry/apis/dashboardsnapshot/register.go index 58ff6367713..611d7d91fc6 100644 --- a/pkg/registry/apis/dashboardsnapshot/register.go +++ b/pkg/registry/apis/dashboardsnapshot/register.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "net/http" - "time" "github.com/gorilla/mux" "github.com/prometheus/client_golang/prometheus" @@ -28,7 +27,6 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" "github.com/grafana/grafana/pkg/services/dashboardsnapshots" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -135,25 +133,7 @@ func (b *SnapshotsAPIBuilder) GetAPIGroupInfo( namespacer: b.namespacer, options: b.options, } - legacyStore.tableConverter = gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The snapshot name"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*dashboardsnapshot.DashboardSnapshot) - if ok { - return []interface{}{ - m.Name, - m.Spec.Title, - m.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - } - return nil, fmt.Errorf("expected snapshot") - }, - ) + legacyStore.tableConverter = resourceInfo.TableConverter() storage[resourceInfo.StoragePath()] = legacyStore storage[resourceInfo.StoragePath("body")] = &subBodyREST{ service: b.service, diff --git a/pkg/registry/apis/datasource/register.go b/pkg/registry/apis/datasource/register.go index 08db4ca0b56..94bf4aed4c1 100644 --- a/pkg/registry/apis/datasource/register.go +++ b/pkg/registry/apis/datasource/register.go @@ -4,7 +4,6 @@ import ( "context" "encoding/json" "fmt" - "time" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/prometheus/client_golang/prometheus" @@ -29,7 +28,6 @@ import ( "github.com/grafana/grafana/pkg/registry/apis/query/queryschema" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/builder" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore" "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource/kinds" @@ -209,29 +207,9 @@ func (b *DataSourceAPIBuilder) GetAPIGroupInfo( conn := b.connectionResourceInfo storage[conn.StoragePath()] = &connectionAccess{ - datasources: b.datasources, - resourceInfo: conn, - tableConverter: gapiutil.NewTableConverter( - conn.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The datasource title"}, - {Name: "APIVersion", Type: "string", Format: "string", Description: "API Version"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*datasource.DataSourceConnection) - if !ok { - return nil, fmt.Errorf("expected connection") - } - return []interface{}{ - m.Name, - m.Title, - m.APIVersion, - m.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - }, - ), + datasources: b.datasources, + resourceInfo: conn, + tableConverter: conn.TableConverter(), } storage[conn.StoragePath("query")] = &subQueryREST{builder: b} storage[conn.StoragePath("health")] = &subHealthREST{builder: b} diff --git a/pkg/registry/apis/featuretoggle/features.go b/pkg/registry/apis/featuretoggle/features.go index e98f1b3569d..e50cf2658e0 100644 --- a/pkg/registry/apis/featuretoggle/features.go +++ b/pkg/registry/apis/featuretoggle/features.go @@ -12,7 +12,6 @@ import ( common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" "github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/featuremgmt" ) @@ -36,25 +35,8 @@ type featuresStorage struct { func NewFeaturesStorage() *featuresStorage { resourceInfo := v0alpha1.FeatureResourceInfo return &featuresStorage{ - resource: &resourceInfo, - tableConverter: gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Stage", Type: "string", Format: "string", Description: "Where is the flag in the dev cycle"}, - {Name: "Owner", Type: "string", Format: "string", Description: "Which team owns the feature"}, - }, - func(obj any) ([]interface{}, error) { - r, ok := obj.(*v0alpha1.Feature) - if ok { - return []interface{}{ - r.Name, - r.Spec.Stage, - r.Spec.Owner, - }, nil - } - return nil, fmt.Errorf("expected resource or info") - }), + resource: &resourceInfo, + tableConverter: resourceInfo.TableConverter(), } } diff --git a/pkg/registry/apis/folders/register.go b/pkg/registry/apis/folders/register.go index f7aa164d3ea..a22989ee283 100644 --- a/pkg/registry/apis/folders/register.go +++ b/pkg/registry/apis/folders/register.go @@ -2,7 +2,6 @@ package folders import ( "context" - "fmt" "github.com/prometheus/client_golang/prometheus" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -17,13 +16,11 @@ import ( "k8s.io/kube-openapi/pkg/spec3" "github.com/grafana/grafana/pkg/apimachinery/identity" - "github.com/grafana/grafana/pkg/apimachinery/utils" "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/folder" @@ -107,27 +104,9 @@ func (b *FolderAPIBuilder) GetAPIGroupInfo( apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(v0alpha1.GROUP, scheme, metav1.ParameterCodec, codecs) legacyStore := &legacyStorage{ - service: b.folderSvc, - namespacer: b.namespacer, - tableConverter: gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The display name"}, - {Name: "Parent", Type: "string", Format: "string", Description: "Parent folder UID"}, - }, - func(obj any) ([]interface{}, error) { - r, ok := obj.(*v0alpha1.Folder) - if ok { - accessor, _ := utils.MetaAccessor(r) - return []interface{}{ - r.Name, - r.Spec.Title, - accessor.GetFolder(), - }, nil - } - return nil, fmt.Errorf("expected resource or info") - }), + service: b.folderSvc, + namespacer: b.namespacer, + tableConverter: resourceInfo.TableConverter(), } storage := map[string]rest.Storage{} diff --git a/pkg/registry/apis/identity/register.go b/pkg/registry/apis/identity/register.go index 3dbf11374ab..38a8869e052 100644 --- a/pkg/registry/apis/identity/register.go +++ b/pkg/registry/apis/identity/register.go @@ -2,8 +2,6 @@ package identity import ( "context" - "fmt" - "time" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -19,7 +17,6 @@ import ( identityapi "github.com/grafana/grafana/pkg/apimachinery/identity" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" "github.com/grafana/grafana/pkg/services/apiserver/builder" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/team" "github.com/grafana/grafana/pkg/services/user" @@ -87,85 +84,25 @@ func (b *IdentityAPIBuilder) GetAPIGroupInfo( team := identity.TeamResourceInfo teamStore := &legacyTeamStorage{ - service: b.svcTeam, - resourceInfo: team, - tableConverter: gapiutil.NewTableConverter( - team.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The team name"}, - {Name: "Email", Type: "string", Format: "string", Description: "team email"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*identity.Team) - if !ok { - return nil, fmt.Errorf("expected playlist") - } - return []interface{}{ - m.Name, - m.Spec.Title, - m.Spec.Email, - m.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - }, - ), + service: b.svcTeam, + resourceInfo: team, + tableConverter: team.TableConverter(), } storage[team.StoragePath()] = teamStore user := identity.UserResourceInfo userStore := &legacyUserStorage{ - service: b.svcUser, - resourceInfo: user, - tableConverter: gapiutil.NewTableConverter( - user.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Login", Type: "string", Format: "string", Description: "The user login"}, - {Name: "Email", Type: "string", Format: "string", Description: "The user email"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - u, ok := obj.(*identity.User) - if ok { - return []interface{}{ - u.Name, - u.Spec.Login, - u.Spec.Email, - u.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - } - return nil, fmt.Errorf("expected user") - }, - ), + service: b.svcUser, + resourceInfo: user, + tableConverter: user.TableConverter(), } storage[user.StoragePath()] = userStore sa := identity.ServiceAccountResourceInfo saStore := &legacyServiceAccountStorage{ - service: b.svcUser, - resourceInfo: sa, - tableConverter: gapiutil.NewTableConverter( - user.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Account", Type: "string", Format: "string", Description: "The service account email"}, - {Name: "Email", Type: "string", Format: "string", Description: "The user email"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - u, ok := obj.(*identity.ServiceAccount) - if ok { - return []interface{}{ - u.Name, - u.Spec.Name, - u.Spec.Email, - u.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - } - return nil, fmt.Errorf("expected user") - }, - ), + service: b.svcUser, + resourceInfo: sa, + tableConverter: sa.TableConverter(), } storage[sa.StoragePath()] = saStore diff --git a/pkg/registry/apis/peakq/storage.go b/pkg/registry/apis/peakq/storage.go index 32df4308595..0f87698399b 100644 --- a/pkg/registry/apis/peakq/storage.go +++ b/pkg/registry/apis/peakq/storage.go @@ -1,10 +1,6 @@ package peakq import ( - "fmt" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" @@ -12,7 +8,6 @@ import ( peakq "github.com/grafana/grafana/pkg/apis/peakq/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" ) var _ grafanarest.Storage = (*storage)(nil) @@ -33,28 +28,10 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (* PredicateFunc: grafanaregistry.Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*peakq.QueryTemplate) - if !ok { - return nil, fmt.Errorf("expected query template") - } - return []interface{}{ - m.Name, - m.Spec.Title, - m.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - }, - ), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, + TableConvertor: resourceInfo.TableConverter(), + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, } options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: grafanaregistry.GetAttrs} if err := store.CompleteWithOptions(options); err != nil { diff --git a/pkg/registry/apis/playlist/register.go b/pkg/registry/apis/playlist/register.go index d8a60244ecf..1dadebf324a 100644 --- a/pkg/registry/apis/playlist/register.go +++ b/pkg/registry/apis/playlist/register.go @@ -1,9 +1,6 @@ package playlist import ( - "fmt" - "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" @@ -21,7 +18,6 @@ import ( "github.com/grafana/grafana/pkg/infra/kvstore" "github.com/grafana/grafana/pkg/services/apiserver/builder" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" playlistsvc "github.com/grafana/grafana/pkg/services/playlist" "github.com/grafana/grafana/pkg/setting" ) @@ -94,27 +90,7 @@ func (b *PlaylistAPIBuilder) GetAPIGroupInfo( service: b.service, namespacer: b.namespacer, } - legacyStore.tableConverter = gapiutil.NewTableConverter( - resource.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Title", Type: "string", Format: "string", Description: "The playlist name"}, - {Name: "Interval", Type: "string", Format: "string", Description: "How often the playlist will update"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*playlist.Playlist) - if !ok { - return nil, fmt.Errorf("expected playlist") - } - return []interface{}{ - m.Name, - m.Spec.Title, - m.Spec.Interval, - m.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - }, - ) + legacyStore.tableConverter = resource.TableConverter() storage[resource.StoragePath()] = legacyStore // enable dual writes if a RESTOptionsGetter is provided diff --git a/pkg/registry/apis/scope/storage.go b/pkg/registry/apis/scope/storage.go index 453eea59712..49a3c94d847 100644 --- a/pkg/registry/apis/scope/storage.go +++ b/pkg/registry/apis/scope/storage.go @@ -2,9 +2,7 @@ package scope import ( "fmt" - "time" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" @@ -15,7 +13,6 @@ import ( scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" ) var _ grafanarest.Storage = (*storage)(nil) @@ -36,30 +33,10 @@ func newScopeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGette PredicateFunc: Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Created At", Type: "date"}, - {Name: "Title", Type: "string"}, - {Name: "Filters", Type: "array"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*scope.Scope) - if !ok { - return nil, fmt.Errorf("expected scope") - } - return []interface{}{ - m.Name, - m.CreationTimestamp.UTC().Format(time.RFC3339), - m.Spec.Title, - m.Spec.Filters, - }, nil - }, - ), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, + TableConvertor: resourceInfo.TableConverter(), + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, } options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs} if err := store.CompleteWithOptions(options); err != nil { @@ -80,30 +57,10 @@ func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic. PredicateFunc: Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Created At", Type: "date"}, - {Name: "Dashboard", Type: "string"}, - {Name: "Scope", Type: "string"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*scope.ScopeDashboardBinding) - if !ok { - return nil, fmt.Errorf("expected scope dashboard binding") - } - return []interface{}{ - m.Name, - m.CreationTimestamp.UTC().Format(time.RFC3339), - m.Spec.Dashboard, - m.Spec.Scope, - }, nil - }, - ), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, + TableConvertor: resourceInfo.TableConverter(), + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, } options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs} if err := store.CompleteWithOptions(options); err != nil { @@ -124,36 +81,10 @@ func newScopeNodeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsG PredicateFunc: Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Created At", Type: "date"}, - {Name: "Title", Type: "string"}, - {Name: "Parent Name", Type: "string"}, - {Name: "Node Type", Type: "string"}, - {Name: "Link Type", Type: "string"}, - {Name: "Link ID", Type: "string"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*scope.ScopeNode) - if !ok { - return nil, fmt.Errorf("expected scope node") - } - return []interface{}{ - m.Name, - m.CreationTimestamp.UTC().Format(time.RFC3339), - m.Spec.Title, - m.Spec.ParentName, - m.Spec.NodeType, - m.Spec.LinkType, - m.Spec.LinkID, - }, nil - }, - ), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, + TableConvertor: resourceInfo.TableConverter(), + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, } options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs} if err := store.CompleteWithOptions(options); err != nil { diff --git a/pkg/registry/apis/service/storage.go b/pkg/registry/apis/service/storage.go index 86b1fbd3a2e..04497ac64aa 100644 --- a/pkg/registry/apis/service/storage.go +++ b/pkg/registry/apis/service/storage.go @@ -1,10 +1,6 @@ package service import ( - "fmt" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/generic" genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" @@ -12,7 +8,6 @@ import ( service "github.com/grafana/grafana/pkg/apis/service/v0alpha1" grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" - gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" ) var _ grafanarest.Storage = (*storage)(nil) @@ -33,28 +28,10 @@ func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (* PredicateFunc: grafanaregistry.Matcher, DefaultQualifiedResource: resourceInfo.GroupResource(), SingularQualifiedResource: resourceInfo.SingularGroupResource(), - TableConvertor: gapiutil.NewTableConverter( - resourceInfo.GroupResource(), - []metav1.TableColumnDefinition{ - {Name: "Name", Type: "string", Format: "name"}, - {Name: "Host", Type: "string", Format: "string", Description: "The service host"}, - {Name: "Created At", Type: "date"}, - }, - func(obj any) ([]interface{}, error) { - m, ok := obj.(*service.ExternalName) - if !ok { - return nil, fmt.Errorf("expected playlist") - } - return []interface{}{ - m.Name, - m.Spec.Host, - m.CreationTimestamp.UTC().Format(time.RFC3339), - }, nil - }, - ), - CreateStrategy: strategy, - UpdateStrategy: strategy, - DeleteStrategy: strategy, + TableConvertor: resourceInfo.TableConverter(), + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, } options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: grafanaregistry.GetAttrs} if err := store.CompleteWithOptions(options); err != nil {