mirror of https://github.com/grafana/grafana
parent
86a7064334
commit
7345ece8ef
@ -0,0 +1,264 @@ |
||||
package access |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"time" |
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils" |
||||
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" |
||||
"github.com/grafana/grafana/pkg/storage/unified/resource" |
||||
) |
||||
|
||||
func getDashbaordFromEvent(event resource.WriteEvent) (*dashboard.Dashboard, error) { |
||||
obj, ok := event.Object.GetRuntimeObject() |
||||
if ok && obj != nil { |
||||
dash, ok := obj.(*dashboard.Dashboard) |
||||
if ok { |
||||
return dash, nil |
||||
} |
||||
} |
||||
dash := &dashboard.Dashboard{} |
||||
err := json.Unmarshal(event.Value, dash) |
||||
return dash, err |
||||
} |
||||
|
||||
func isDashboardKey(key *resource.ResourceKey, requireName bool) error { |
||||
gr := dashboard.DashboardResourceInfo.GroupResource() |
||||
if key.Group != gr.Group { |
||||
return fmt.Errorf("expecting dashboard group") |
||||
} |
||||
if key.Resource != gr.Resource { |
||||
return fmt.Errorf("expecting dashboard resource") |
||||
} |
||||
if requireName && key.Name == "" { |
||||
return fmt.Errorf("expecting dashboard name (uid)") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (a *dashboardSqlAccess) WriteEvent(ctx context.Context, event resource.WriteEvent) (rv int64, err error) { |
||||
info, err := request.ParseNamespace(event.Key.Namespace) |
||||
if err == nil { |
||||
err = isDashboardKey(event.Key, true) |
||||
} |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
switch event.Type { |
||||
case resource.WatchEvent_DELETED: |
||||
{ |
||||
_, _, err = a.DeleteDashboard(ctx, info.OrgID, event.Key.Name) |
||||
rv = event.EventID |
||||
} |
||||
// The difference depends on embedded internal ID
|
||||
case resource.WatchEvent_ADDED, resource.WatchEvent_MODIFIED: |
||||
{ |
||||
dash, err := getDashbaordFromEvent(event) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
after, _, err := a.SaveDashboard(ctx, info.OrgID, dash) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
if after != nil { |
||||
meta, err := utils.MetaAccessor(after) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
rv, err = meta.GetResourceVersionInt64() |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
} |
||||
} |
||||
default: |
||||
return 0, fmt.Errorf("unsupported event type: %v", event.Type) |
||||
} |
||||
|
||||
// Async notify all subscribers (not HA!!!)
|
||||
if a.subscribers != nil { |
||||
go func() { |
||||
write := &resource.WrittenEvent{ |
||||
WriteEvent: event, |
||||
|
||||
Timestamp: time.Now().UnixMilli(), |
||||
ResourceVersion: rv, |
||||
} |
||||
for _, sub := range a.subscribers { |
||||
sub <- write |
||||
} |
||||
}() |
||||
} |
||||
return rv, err |
||||
} |
||||
|
||||
// Read implements ResourceStoreServer.
|
||||
func (a *dashboardSqlAccess) GetDashboard(ctx context.Context, orgId int64, uid string) (*dashboard.Dashboard, int64, error) { |
||||
rows, _, err := a.getRows(ctx, &DashboardQuery{ |
||||
OrgID: orgId, |
||||
UID: uid, |
||||
Limit: 100, // will only be one!
|
||||
}) |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
defer func() { _ = rows.Close() }() |
||||
|
||||
row, err := rows.Next() |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
|
||||
return row.Dash, row.ResourceVersion, nil |
||||
} |
||||
|
||||
// Read implements ResourceStoreServer.
|
||||
func (a *dashboardSqlAccess) Read(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResponse, error) { |
||||
info, err := request.ParseNamespace(req.Key.Namespace) |
||||
if err == nil { |
||||
err = isDashboardKey(req.Key, true) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if req.ResourceVersion > 0 { |
||||
return nil, fmt.Errorf("reading from history not yet supported") |
||||
} |
||||
|
||||
dash, rv, err := a.GetDashboard(ctx, info.OrgID, req.Key.Name) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
value, err := json.Marshal(dash) |
||||
return &resource.ReadResponse{ |
||||
ResourceVersion: rv, |
||||
Value: value, |
||||
}, err |
||||
} |
||||
|
||||
// List implements AppendingStore.
|
||||
func (a *dashboardSqlAccess) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResponse, error) { |
||||
opts := req.Options |
||||
info, err := request.ParseNamespace(opts.Key.Namespace) |
||||
if err == nil { |
||||
err = isDashboardKey(opts.Key, false) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
token, err := readContinueToken(req.NextPageToken) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if token.orgId > 0 && token.orgId != info.OrgID { |
||||
return nil, fmt.Errorf("token and orgID mismatch") |
||||
} |
||||
|
||||
query := &DashboardQuery{ |
||||
OrgID: info.OrgID, |
||||
Limit: int(req.Limit), |
||||
MaxBytes: 2 * 1024 * 1024, // 2MB,
|
||||
MinID: token.id, |
||||
Labels: req.Options.Labels, |
||||
} |
||||
|
||||
rows, limit, err := a.getRows(ctx, query) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
defer func() { _ = rows.Close() }() |
||||
|
||||
totalSize := 0 |
||||
list := &resource.ListResponse{} |
||||
for { |
||||
row, err := rows.Next() |
||||
if err != nil || row == nil { |
||||
return list, err |
||||
} |
||||
|
||||
totalSize += row.Bytes |
||||
if len(list.Items) > 0 && (totalSize > query.MaxBytes || len(list.Items) >= limit) { |
||||
// if query.Requirements.Folder != nil {
|
||||
// row.token.folder = *query.Requirements.Folder
|
||||
// }
|
||||
list.NextPageToken = row.token.String() // will skip this one but start here next time
|
||||
return list, err |
||||
} |
||||
// TODO -- make it smaller and stick the body as an annotation...
|
||||
val, err := json.Marshal(row.Dash) |
||||
if err != nil { |
||||
return list, err |
||||
} |
||||
list.Items = append(list.Items, &resource.ResourceWrapper{ |
||||
ResourceVersion: row.ResourceVersion, |
||||
Value: val, |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// Watch implements AppendingStore.
|
||||
func (a *dashboardSqlAccess) WatchWriteEvents(ctx context.Context) (<-chan *resource.WrittenEvent, error) { |
||||
stream := make(chan *resource.WrittenEvent, 10) |
||||
{ |
||||
a.mutex.Lock() |
||||
defer a.mutex.Unlock() |
||||
|
||||
// Add the event stream
|
||||
a.subscribers = append(a.subscribers, stream) |
||||
} |
||||
|
||||
// Wait for context done
|
||||
go func() { |
||||
// Wait till the context is done
|
||||
<-ctx.Done() |
||||
|
||||
// Then remove the subscription
|
||||
a.mutex.Lock() |
||||
defer a.mutex.Unlock() |
||||
|
||||
// Copy all streams without our listener
|
||||
subs := []chan *resource.WrittenEvent{} |
||||
for _, sub := range a.subscribers { |
||||
if sub != stream { |
||||
subs = append(subs, sub) |
||||
} |
||||
} |
||||
a.subscribers = subs |
||||
}() |
||||
return stream, nil |
||||
} |
||||
|
||||
func (a *dashboardSqlAccess) SupportsSignedURLs() bool { |
||||
return false |
||||
} |
||||
|
||||
func (a *dashboardSqlAccess) PutBlob(context.Context, *resource.PutBlobRequest) (*resource.PutBlobResponse, error) { |
||||
return nil, fmt.Errorf("not implemented yet") |
||||
} |
||||
|
||||
func (a *dashboardSqlAccess) GetBlob(ctx context.Context, key *resource.ResourceKey, info *utils.BlobInfo, mustProxy bool) (*resource.GetBlobResponse, error) { |
||||
ns, err := request.ParseNamespace(key.Namespace) |
||||
if err == nil { |
||||
err = isDashboardKey(key, true) |
||||
} |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
dash, _, err := a.GetDashboard(ctx, ns.OrgID, key.Name) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
rsp := &resource.GetBlobResponse{ |
||||
ContentType: "application/json", |
||||
} |
||||
rsp.Value, err = json.Marshal(dash.Spec) |
||||
return rsp, err |
||||
} |
@ -1,315 +1,66 @@ |
||||
package dashboard |
||||
|
||||
import ( |
||||
"context" |
||||
"encoding/json" |
||||
"fmt" |
||||
"sync" |
||||
"time" |
||||
|
||||
"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" |
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" |
||||
"github.com/grafana/grafana/pkg/apimachinery/utils" |
||||
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1" |
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" |
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboard/access" |
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" |
||||
"github.com/grafana/grafana/pkg/storage/unified/apistore" |
||||
"github.com/grafana/grafana/pkg/storage/unified/resource" |
||||
) |
||||
|
||||
var ( |
||||
_ resource.AppendingStore = (*dashboardStorage)(nil) |
||||
_ resource.BlobStore = (*dashboardStorage)(nil) |
||||
) |
||||
|
||||
type dashboardStorage struct { |
||||
resource common.ResourceInfo |
||||
access access.DashboardAccess |
||||
tableConverter rest.TableConvertor |
||||
|
||||
// Typically one... the server wrapper
|
||||
subscribers []chan *resource.WrittenEvent |
||||
mutex sync.Mutex |
||||
} |
||||
|
||||
// func (s *dashboardStorage) Create(ctx context.Context,
|
||||
// obj runtime.Object,
|
||||
// createValidation rest.ValidateObjectFunc,
|
||||
// options *metav1.CreateOptions,
|
||||
// ) (runtime.Object, error) {
|
||||
// info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// p, ok := obj.(*v0alpha1.Dashboard)
|
||||
// if !ok {
|
||||
// return nil, fmt.Errorf("expected dashboard?")
|
||||
// }
|
||||
|
||||
// // HACK to simplify unique name testing from kubectl
|
||||
// t := p.Spec.GetNestedString("title")
|
||||
// if strings.Contains(t, "${NOW}") {
|
||||
// t = strings.ReplaceAll(t, "${NOW}", fmt.Sprintf("%d", time.Now().Unix()))
|
||||
// p.Spec.Set("title", t)
|
||||
// }
|
||||
|
||||
// dash, _, err := s.access.SaveDashboard(ctx, info.OrgID, p)
|
||||
// return dash, err
|
||||
// }
|
||||
|
||||
// func (s *dashboardStorage) Update(ctx context.Context,
|
||||
// name string,
|
||||
// objInfo rest.UpdatedObjectInfo,
|
||||
// createValidation rest.ValidateObjectFunc,
|
||||
// updateValidation rest.ValidateObjectUpdateFunc,
|
||||
// forceAllowCreate bool,
|
||||
// options *metav1.UpdateOptions,
|
||||
// ) (runtime.Object, bool, error) {
|
||||
// info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
// if err != nil {
|
||||
// return nil, false, err
|
||||
// }
|
||||
|
||||
// created := false
|
||||
// old, err := s.Get(ctx, name, nil)
|
||||
// if err != nil {
|
||||
// return old, created, err
|
||||
// }
|
||||
|
||||
// obj, err := objInfo.UpdatedObject(ctx, old)
|
||||
// if err != nil {
|
||||
// return old, created, err
|
||||
// }
|
||||
// p, ok := obj.(*v0alpha1.Dashboard)
|
||||
// if !ok {
|
||||
// return nil, created, fmt.Errorf("expected dashboard after update")
|
||||
// }
|
||||
|
||||
// _, created, err = s.access.SaveDashboard(ctx, info.OrgID, p)
|
||||
// if err == nil {
|
||||
// r, err := s.Get(ctx, name, nil)
|
||||
// return r, created, err
|
||||
// }
|
||||
// return nil, created, err
|
||||
// }
|
||||
|
||||
// // GracefulDeleter
|
||||
// func (s *dashboardStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
// info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
// if err != nil {
|
||||
// return nil, false, err
|
||||
// }
|
||||
|
||||
// return s.access.DeleteDashboard(ctx, info.OrgID, name)
|
||||
// }
|
||||
|
||||
// func (s *dashboardStorage) ListXX(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
// orgId, err := request.OrgIDForList(ctx)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// // fmt.Printf("LIST: %s\n", options.Continue)
|
||||
|
||||
// // translate grafana.app/* label selectors into field requirements
|
||||
// requirements, newSelector, err := entity.ReadLabelSelectors(options.LabelSelector)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
// query := &access.DashboardQuery{
|
||||
// OrgID: orgId,
|
||||
// Limit: int(options.Limit),
|
||||
// MaxBytes: 2 * 1024 * 1024, // 2MB,
|
||||
// ContinueToken: options.Continue,
|
||||
// Requirements: requirements,
|
||||
// Labels: newSelector,
|
||||
// }
|
||||
|
||||
// return s.access.GetDashboards(ctx, query)
|
||||
// }
|
||||
|
||||
func (s *dashboardStorage) SupportsSignedURLs() bool { |
||||
return false |
||||
} |
||||
|
||||
func (s *dashboardStorage) PutBlob(context.Context, *resource.PutBlobRequest) (*resource.PutBlobResponse, error) { |
||||
return nil, fmt.Errorf("not implemented yet") |
||||
} |
||||
|
||||
func (s *dashboardStorage) GetBlob(ctx context.Context, resource *resource.ResourceKey, info *utils.BlobInfo, mustProxy bool) (*resource.GetBlobResponse, error) { |
||||
return nil, fmt.Errorf("not implemented yet") |
||||
} |
||||
|
||||
func getDashbaord(event resource.WriteEvent) (*dashboard.Dashboard, error) { |
||||
obj, ok := event.Object.GetRuntimeObject() |
||||
if ok && obj != nil { |
||||
dash, ok := obj.(*dashboard.Dashboard) |
||||
if ok { |
||||
return dash, nil |
||||
} |
||||
} |
||||
dash := &dashboard.Dashboard{} |
||||
err := json.Unmarshal(event.Value, dash) |
||||
return dash, err |
||||
} |
||||
|
||||
func isDashboardKey(key *resource.ResourceKey, requireName bool) error { |
||||
gr := dashboard.DashboardResourceInfo.GroupResource() |
||||
if key.Group != gr.Group { |
||||
return fmt.Errorf("expecting dashboard group") |
||||
} |
||||
if key.Resource != gr.Resource { |
||||
return fmt.Errorf("expecting dashboard resource") |
||||
} |
||||
if requireName && key.Name == "" { |
||||
return fmt.Errorf("expecting dashboard name (uid)") |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func (s *dashboardStorage) WriteEvent(ctx context.Context, event resource.WriteEvent) (rv int64, err error) { |
||||
info, err := request.ParseNamespace(event.Key.Namespace) |
||||
if err == nil { |
||||
err = isDashboardKey(event.Key, true) |
||||
} |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
switch event.Type { |
||||
case resource.WatchEvent_DELETED: |
||||
{ |
||||
_, _, err = s.access.DeleteDashboard(ctx, info.OrgID, event.Key.Name) |
||||
rv = event.EventID |
||||
} |
||||
// The difference depends on embedded internal ID
|
||||
case resource.WatchEvent_ADDED, resource.WatchEvent_MODIFIED: |
||||
{ |
||||
dash, err := getDashbaord(event) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
|
||||
after, _, err := s.access.SaveDashboard(ctx, info.OrgID, dash) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
if after != nil { |
||||
meta, err := utils.MetaAccessor(after) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
rv, err = meta.GetResourceVersionInt64() |
||||
} |
||||
} |
||||
default: |
||||
return 0, fmt.Errorf("unsupported event type: %v", event.Type) |
||||
} |
||||
|
||||
// Async notify all subscribers (not HA!!!)
|
||||
if s.subscribers != nil { |
||||
go func() { |
||||
write := &resource.WrittenEvent{ |
||||
WriteEvent: event, |
||||
|
||||
Timestamp: time.Now().UnixMilli(), |
||||
ResourceVersion: rv, |
||||
} |
||||
for _, sub := range s.subscribers { |
||||
sub <- write |
||||
} |
||||
}() |
||||
} |
||||
return rv, err |
||||
} |
||||
|
||||
// Read implements ResourceStoreServer.
|
||||
func (s *dashboardStorage) Read(ctx context.Context, req *resource.ReadRequest) (*resource.ReadResponse, error) { |
||||
info, err := request.ParseNamespace(req.Key.Namespace) |
||||
if err == nil { |
||||
err = isDashboardKey(req.Key, true) |
||||
} |
||||
func (s *dashboardStorage) newStore(scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter) (rest.Storage, error) { |
||||
server, err := resource.NewResourceServer(resource.ResourceServerOptions{ |
||||
Store: s.access, |
||||
Blob: s.access, |
||||
// WriteAccess: resource.WriteAccessHooks{
|
||||
// Folder: func(ctx context.Context, user identity.Requester, uid string) bool {
|
||||
// // ???
|
||||
// },
|
||||
// },
|
||||
}) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if req.ResourceVersion > 0 { |
||||
return nil, fmt.Errorf("reading from history not yet supported") |
||||
} |
||||
dash, err := s.access.GetDashboard(ctx, info.OrgID, req.Key.Name) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
meta, err := utils.MetaAccessor(dash) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
rv, err := meta.GetResourceVersionInt64() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
value, err := json.Marshal(dash) |
||||
return &resource.ReadResponse{ |
||||
ResourceVersion: rv, |
||||
Value: value, |
||||
}, err |
||||
} |
||||
|
||||
// List implements AppendingStore.
|
||||
func (s *dashboardStorage) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResponse, error) { |
||||
opts := req.Options |
||||
info, err := request.ParseNamespace(opts.Key.Namespace) |
||||
if err == nil { |
||||
err = isDashboardKey(opts.Key, false) |
||||
} |
||||
resourceInfo := s.resource |
||||
defaultOpts, err := defaultOptsGetter.GetRESTOptions(resourceInfo.GroupResource()) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
query := &access.DashboardQuery{ |
||||
OrgID: info.OrgID, |
||||
Limit: int(req.Limit), |
||||
MaxBytes: 2 * 1024 * 1024, // 2MB,
|
||||
ContinueToken: req.NextPageToken, |
||||
// Requirements: requirements,
|
||||
// Labels: newSelector,
|
||||
} |
||||
fmt.Printf("%+v\n", query) |
||||
|
||||
// return s.access.GetDashboards(ctx, query)
|
||||
|
||||
return nil, fmt.Errorf("todo") |
||||
} |
||||
|
||||
// Watch implements AppendingStore.
|
||||
func (s *dashboardStorage) WatchWriteEvents(ctx context.Context) (<-chan *resource.WrittenEvent, error) { |
||||
stream := make(chan *resource.WrittenEvent, 10) |
||||
{ |
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
|
||||
// Add the event stream
|
||||
s.subscribers = append(s.subscribers, stream) |
||||
} |
||||
|
||||
// Wait for context done
|
||||
go func() { |
||||
// Wait till the context is done
|
||||
<-ctx.Done() |
||||
|
||||
// Then remove the subscription
|
||||
s.mutex.Lock() |
||||
defer s.mutex.Unlock() |
||||
|
||||
// Copy all streams without our listener
|
||||
subs := []chan *resource.WrittenEvent{} |
||||
for _, sub := range s.subscribers { |
||||
if sub != stream { |
||||
subs = append(subs, sub) |
||||
} |
||||
client := resource.NewLocalResourceStoreClient(server) |
||||
optsGetter := apistore.NewRESTOptionsGetter(client, |
||||
defaultOpts.StorageConfig.Codec, |
||||
) |
||||
|
||||
strategy := grafanaregistry.NewStrategy(scheme) |
||||
store := &genericregistry.Store{ |
||||
NewFunc: resourceInfo.NewFunc, |
||||
NewListFunc: resourceInfo.NewListFunc, |
||||
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()), |
||||
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()), |
||||
PredicateFunc: grafanaregistry.Matcher, |
||||
DefaultQualifiedResource: resourceInfo.GroupResource(), |
||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(), |
||||
CreateStrategy: strategy, |
||||
UpdateStrategy: strategy, |
||||
DeleteStrategy: strategy, |
||||
TableConvertor: s.tableConverter, |
||||
} |
||||
|
||||
options := &generic.StoreOptions{RESTOptions: optsGetter} |
||||
if err := store.CompleteWithOptions(options); err != nil { |
||||
return nil, err |
||||
} |
||||
s.subscribers = subs |
||||
}() |
||||
return stream, nil |
||||
return store, err |
||||
} |
||||
|
@ -1,60 +0,0 @@ |
||||
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) |
||||
|
||||
type storage struct { |
||||
*genericregistry.Store |
||||
} |
||||
|
||||
func newStorage(scheme *runtime.Scheme) (*storage, error) { |
||||
strategy := grafanaregistry.NewStrategy(scheme) |
||||
resourceInfo := v0alpha1.DashboardResourceInfo |
||||
store := &genericregistry.Store{ |
||||
NewFunc: resourceInfo.NewFunc, |
||||
NewListFunc: resourceInfo.NewListFunc, |
||||
KeyRootFunc: grafanaregistry.KeyRootFunc(resourceInfo.GroupResource()), |
||||
KeyFunc: grafanaregistry.NamespaceKeyFunc(resourceInfo.GroupResource()), |
||||
PredicateFunc: grafanaregistry.Matcher, |
||||
DefaultQualifiedResource: resourceInfo.GroupResource(), |
||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(), |
||||
CreateStrategy: strategy, |
||||
UpdateStrategy: strategy, |
||||
DeleteStrategy: strategy, |
||||
} |
||||
|
||||
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 |
||||
} |
Loading…
Reference in new issue