mirror of
				https://github.com/grafana/grafana.git
				synced 2025-02-25 18:55:37 -06:00 
			
		
		
		
	now with dashboard history
This commit is contained in:
		@@ -19,6 +19,7 @@ import (
 | 
			
		||||
	"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"
 | 
			
		||||
	dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/services/provisioning"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/services/sqlstore/session"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/storage/unified/resource"
 | 
			
		||||
@@ -55,19 +56,25 @@ type dashboardSqlAccess struct {
 | 
			
		||||
	namespacer   request.NamespaceMapper
 | 
			
		||||
	dashStore    dashboards.Store
 | 
			
		||||
	provisioning provisioning.ProvisioningService
 | 
			
		||||
	versions     dashver.Service
 | 
			
		||||
 | 
			
		||||
	// Typically one... the server wrapper
 | 
			
		||||
	subscribers []chan *resource.WrittenEvent
 | 
			
		||||
	mutex       sync.Mutex
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewDashboardAccess(sql db.DB, namespacer request.NamespaceMapper, dashStore dashboards.Store, provisioning provisioning.ProvisioningService) DashboardAccess {
 | 
			
		||||
func NewDashboardAccess(sql db.DB,
 | 
			
		||||
	namespacer request.NamespaceMapper,
 | 
			
		||||
	dashStore dashboards.Store,
 | 
			
		||||
	provisioning provisioning.ProvisioningService,
 | 
			
		||||
	versions dashver.Service) DashboardAccess {
 | 
			
		||||
	return &dashboardSqlAccess{
 | 
			
		||||
		sql:          sql,
 | 
			
		||||
		sess:         sql.GetSqlxSession(),
 | 
			
		||||
		namespacer:   namespacer,
 | 
			
		||||
		dashStore:    dashStore,
 | 
			
		||||
		provisioning: provisioning,
 | 
			
		||||
		versions:     versions,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -6,9 +6,12 @@ import (
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
			
		||||
 | 
			
		||||
	"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"
 | 
			
		||||
	dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/storage/unified/resource"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
@@ -241,7 +244,7 @@ func (a *dashboardSqlAccess) SupportsSignedURLs() bool {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dashboardSqlAccess) PutBlob(context.Context, *resource.PutBlobRequest) (*resource.PutBlobResponse, error) {
 | 
			
		||||
	return nil, fmt.Errorf("not implemented yet")
 | 
			
		||||
	return nil, fmt.Errorf("put blob not implemented yet")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dashboardSqlAccess) GetBlob(ctx context.Context, key *resource.ResourceKey, info *utils.BlobInfo, mustProxy bool) (*resource.GetBlobResponse, error) {
 | 
			
		||||
@@ -262,3 +265,51 @@ func (a *dashboardSqlAccess) GetBlob(ctx context.Context, key *resource.Resource
 | 
			
		||||
	rsp.Value, err = json.Marshal(dash.Spec)
 | 
			
		||||
	return rsp, err
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryRequest) (*resource.HistoryResponse, error) {
 | 
			
		||||
	ns, err := request.ParseNamespace(req.Key.Namespace)
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		err = isDashboardKey(req.Key, true)
 | 
			
		||||
	}
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	versions, err := a.versions.List(ctx, &dashver.ListDashboardVersionsQuery{
 | 
			
		||||
		OrgID:        ns.OrgID,
 | 
			
		||||
		DashboardUID: req.Key.Name,
 | 
			
		||||
		Limit:        100,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rsp := &resource.HistoryResponse{}
 | 
			
		||||
	for _, version := range versions {
 | 
			
		||||
		partial := &metav1.PartialObjectMetadata{}
 | 
			
		||||
		meta, err := utils.MetaAccessor(partial)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		meta.SetName(version.DashboardUID)
 | 
			
		||||
		meta.SetCreationTimestamp(metav1.NewTime(version.Created)) // ???
 | 
			
		||||
		meta.SetUpdatedTimestampMillis(version.Created.UnixMilli())
 | 
			
		||||
		meta.SetMessage(version.Message)
 | 
			
		||||
		meta.SetResourceVersionInt64(version.Created.UnixMilli())
 | 
			
		||||
 | 
			
		||||
		bytes, err := json.Marshal(partial)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, err
 | 
			
		||||
		}
 | 
			
		||||
		rsp.Items = append(rsp.Items, &resource.ResourceMeta{
 | 
			
		||||
			ResourceVersion:   version.Created.UnixMilli(),
 | 
			
		||||
			PartialObjectMeta: bytes,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
	return rsp, err
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Used for efficient provisioning
 | 
			
		||||
func (a *dashboardSqlAccess) Origin(context.Context, *resource.OriginRequest) (*resource.OriginResponse, error) {
 | 
			
		||||
	return nil, fmt.Errorf("not yet (origin)")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@ type DashboardQuery struct {
 | 
			
		||||
type DashboardAccess interface {
 | 
			
		||||
	resource.AppendingStore
 | 
			
		||||
	resource.BlobStore
 | 
			
		||||
	resource.ResourceSearchServer
 | 
			
		||||
 | 
			
		||||
	GetDashboard(ctx context.Context, orgId int64, uid string) (*dashboardsV0.Dashboard, int64, error)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -17,12 +17,15 @@ type dashboardStorage struct {
 | 
			
		||||
	resource       common.ResourceInfo
 | 
			
		||||
	access         access.DashboardAccess
 | 
			
		||||
	tableConverter rest.TableConvertor
 | 
			
		||||
 | 
			
		||||
	server resource.ResourceServer
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
		Store:  s.access,
 | 
			
		||||
		Search: s.access,
 | 
			
		||||
		Blob:   s.access,
 | 
			
		||||
		// WriteAccess: resource.WriteAccessHooks{
 | 
			
		||||
		// 	Folder: func(ctx context.Context, user identity.Requester, uid string) bool {
 | 
			
		||||
		// 		// ???
 | 
			
		||||
@@ -32,6 +35,7 @@ func (s *dashboardStorage) newStore(scheme *runtime.Scheme, defaultOptsGetter ge
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	s.server = server
 | 
			
		||||
 | 
			
		||||
	resourceInfo := s.resource
 | 
			
		||||
	defaultOpts, err := defaultOptsGetter.GetRESTOptions(resourceInfo.GroupResource())
 | 
			
		||||
 
 | 
			
		||||
@@ -39,9 +39,8 @@ var _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil)
 | 
			
		||||
type DashboardsAPIBuilder struct {
 | 
			
		||||
	dashboardService dashboards.DashboardService
 | 
			
		||||
 | 
			
		||||
	dashboardVersionService dashver.Service
 | 
			
		||||
	accessControl           accesscontrol.AccessControl
 | 
			
		||||
	store                   *dashboardStorage
 | 
			
		||||
	accessControl accesscontrol.AccessControl
 | 
			
		||||
	store         *dashboardStorage
 | 
			
		||||
 | 
			
		||||
	log log.Logger
 | 
			
		||||
}
 | 
			
		||||
@@ -65,13 +64,12 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
 | 
			
		||||
	builder := &DashboardsAPIBuilder{
 | 
			
		||||
		log: log.New("grafana-apiserver.dashboards"),
 | 
			
		||||
 | 
			
		||||
		dashboardService:        dashboardService,
 | 
			
		||||
		dashboardVersionService: dashboardVersionService,
 | 
			
		||||
		accessControl:           accessControl,
 | 
			
		||||
		dashboardService: dashboardService,
 | 
			
		||||
		accessControl:    accessControl,
 | 
			
		||||
 | 
			
		||||
		store: &dashboardStorage{
 | 
			
		||||
			resource: dashboard.DashboardResourceInfo,
 | 
			
		||||
			access:   access.NewDashboardAccess(sql, namespacer, dashStore, provisioning),
 | 
			
		||||
			access:   access.NewDashboardAccess(sql, namespacer, dashStore, provisioning, dashboardVersionService),
 | 
			
		||||
			tableConverter: gapiutil.NewTableConverter(
 | 
			
		||||
				dashboard.DashboardResourceInfo.GroupResource(),
 | 
			
		||||
				[]metav1.TableColumnDefinition{
 | 
			
		||||
@@ -114,6 +112,8 @@ func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
 | 
			
		||||
		&v0alpha1.DashboardWithAccessInfo{},
 | 
			
		||||
		&v0alpha1.DashboardVersionList{},
 | 
			
		||||
		&v0alpha1.VersionsQueryOptions{},
 | 
			
		||||
		&metav1.PartialObjectMetadata{},
 | 
			
		||||
		&metav1.PartialObjectMetadataList{},
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -158,7 +158,7 @@ func (b *DashboardsAPIBuilder) GetAPIGroupInfo(
 | 
			
		||||
		builder: b,
 | 
			
		||||
	}
 | 
			
		||||
	storage[dash.StoragePath("versions")] = &VersionsREST{
 | 
			
		||||
		builder: b,
 | 
			
		||||
		search: b.store.server, // resource.NewLocalResourceSearchClient(b.store.server),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// // Dual writes if a RESTOptionsGetter is provided
 | 
			
		||||
 
 | 
			
		||||
@@ -2,7 +2,7 @@ package dashboard
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strconv"
 | 
			
		||||
	"strings"
 | 
			
		||||
@@ -11,21 +11,21 @@ import (
 | 
			
		||||
	"k8s.io/apimachinery/pkg/runtime"
 | 
			
		||||
	"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"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
 | 
			
		||||
	dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
 | 
			
		||||
	"github.com/grafana/grafana/pkg/storage/unified/resource"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type VersionsREST struct {
 | 
			
		||||
	builder *DashboardsAPIBuilder
 | 
			
		||||
	search resource.ResourceSearchServer // should be a client!
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var _ = rest.Connecter(&VersionsREST{})
 | 
			
		||||
var _ = rest.StorageMetadata(&VersionsREST{})
 | 
			
		||||
 | 
			
		||||
func (r *VersionsREST) New() runtime.Object {
 | 
			
		||||
	return &dashboard.DashboardVersionList{}
 | 
			
		||||
	return &metav1.PartialObjectMetadataList{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *VersionsREST) Destroy() {
 | 
			
		||||
@@ -40,7 +40,7 @@ func (r *VersionsREST) ProducesMIMETypes(verb string) []string {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *VersionsREST) ProducesObject(verb string) interface{} {
 | 
			
		||||
	return &dashboard.DashboardVersionList{}
 | 
			
		||||
	return &metav1.PartialObjectMetadataList{}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (r *VersionsREST) NewConnectOptions() (runtime.Object, bool, string) {
 | 
			
		||||
@@ -52,66 +52,74 @@ func (r *VersionsREST) Connect(ctx context.Context, uid string, opts runtime.Obj
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
	key := &resource.ResourceKey{
 | 
			
		||||
		Namespace: info.Value,
 | 
			
		||||
		Group:     dashboard.GROUP,
 | 
			
		||||
		Resource:  dashboard.DashboardResourceInfo.GroupResource().Resource,
 | 
			
		||||
		Name:      uid,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
 | 
			
		||||
		path := req.URL.Path
 | 
			
		||||
		idx := strings.LastIndex(path, "/versions/")
 | 
			
		||||
		if idx > 0 {
 | 
			
		||||
			key := path[strings.LastIndex(path, "/")+1:]
 | 
			
		||||
			version, err := strconv.Atoi(key)
 | 
			
		||||
			vkey := path[strings.LastIndex(path, "/")+1:]
 | 
			
		||||
			version, err := strconv.ParseInt(vkey, 10, 64)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				responder.Error(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			dto, err := r.builder.dashboardVersionService.Get(ctx, &dashver.GetDashboardVersionQuery{
 | 
			
		||||
				DashboardUID: uid,
 | 
			
		||||
				OrgID:        info.OrgID,
 | 
			
		||||
				Version:      version,
 | 
			
		||||
			dashbytes, err := r.search.Read(ctx, &resource.ReadRequest{
 | 
			
		||||
				Key:             key,
 | 
			
		||||
				ResourceVersion: version,
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				responder.Error(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			data, _ := dto.Data.Map()
 | 
			
		||||
 | 
			
		||||
			// Convert the version to a regular dashboard
 | 
			
		||||
			dash := &dashboard.Dashboard{
 | 
			
		||||
				ObjectMeta: metav1.ObjectMeta{
 | 
			
		||||
					Name:              uid,
 | 
			
		||||
					CreationTimestamp: metav1.NewTime(dto.Created),
 | 
			
		||||
				},
 | 
			
		||||
				Spec: common.Unstructured{Object: data},
 | 
			
		||||
			dash := &dashboard.Dashboard{}
 | 
			
		||||
			json.Unmarshal(dashbytes.Value, dash)
 | 
			
		||||
			meta, err := utils.MetaAccessor(dash)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				responder.Error(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			meta.SetResourceVersionInt64(dashbytes.ResourceVersion)
 | 
			
		||||
			responder.Object(100, dash)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// Or list versions
 | 
			
		||||
		rsp, err := r.builder.dashboardVersionService.List(ctx, &dashver.ListDashboardVersionsQuery{
 | 
			
		||||
			DashboardUID: uid,
 | 
			
		||||
			OrgID:        info.OrgID,
 | 
			
		||||
		rsp, err := r.search.History(ctx, &resource.HistoryRequest{
 | 
			
		||||
			NextPageToken: "", // TODO!
 | 
			
		||||
			Limit:         100,
 | 
			
		||||
			Key:           key,
 | 
			
		||||
		})
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			responder.Error(err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		versions := &dashboard.DashboardVersionList{}
 | 
			
		||||
		for _, v := range rsp {
 | 
			
		||||
			info := dashboard.DashboardVersionInfo{
 | 
			
		||||
				Version: v.Version,
 | 
			
		||||
				Created: v.Created.UnixMilli(),
 | 
			
		||||
				Message: v.Message,
 | 
			
		||||
			}
 | 
			
		||||
			if v.ParentVersion != v.Version {
 | 
			
		||||
				info.ParentVersion = v.ParentVersion
 | 
			
		||||
			}
 | 
			
		||||
			if v.CreatedBy > 0 {
 | 
			
		||||
				info.CreatedBy = fmt.Sprintf("%d", v.CreatedBy)
 | 
			
		||||
			}
 | 
			
		||||
			versions.Items = append(versions.Items, info)
 | 
			
		||||
 | 
			
		||||
		list := &metav1.PartialObjectMetadataList{
 | 
			
		||||
			ListMeta: metav1.ListMeta{
 | 
			
		||||
				Continue: rsp.NextPageToken,
 | 
			
		||||
			},
 | 
			
		||||
		}
 | 
			
		||||
		responder.Object(http.StatusOK, versions)
 | 
			
		||||
		if rsp.ResourceVersion > 0 {
 | 
			
		||||
			list.ResourceVersion = strconv.FormatInt(rsp.ResourceVersion, 10)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for _, v := range rsp.Items {
 | 
			
		||||
			partial := metav1.PartialObjectMetadata{}
 | 
			
		||||
			err = json.Unmarshal(v.PartialObjectMeta, &partial)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				responder.Error(err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			list.Items = append(list.Items, partial)
 | 
			
		||||
		}
 | 
			
		||||
		responder.Object(http.StatusOK, list)
 | 
			
		||||
	}), nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,22 @@ func NewLocalResourceStoreClient(server ResourceStoreServer) ResourceStoreClient
 | 
			
		||||
	return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewLocalResourceSearchClient(server ResourceStoreServer) ResourceSearchClient {
 | 
			
		||||
	channel := &inprocgrpc.Channel{}
 | 
			
		||||
 | 
			
		||||
	auth := &grpcUtils.Authenticator{}
 | 
			
		||||
 | 
			
		||||
	channel.RegisterService(
 | 
			
		||||
		grpchan.InterceptServer(
 | 
			
		||||
			&ResourceStore_ServiceDesc,
 | 
			
		||||
			grpcAuth.UnaryServerInterceptor(auth.Authenticate),
 | 
			
		||||
			grpcAuth.StreamServerInterceptor(auth.Authenticate),
 | 
			
		||||
		),
 | 
			
		||||
		server,
 | 
			
		||||
	)
 | 
			
		||||
	return NewResourceSearchClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func NewResourceStoreClientGRPC(channel *grpc.ClientConn) ResourceStoreClient {
 | 
			
		||||
	return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor))
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -140,7 +140,7 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) {
 | 
			
		||||
		opts.Blob = &noopService{}
 | 
			
		||||
	}
 | 
			
		||||
	if opts.Diagnostics == nil {
 | 
			
		||||
		opts.Search = &noopService{}
 | 
			
		||||
		opts.Diagnostics = &noopService{}
 | 
			
		||||
	}
 | 
			
		||||
	if opts.Now == nil {
 | 
			
		||||
		opts.Now = func() int64 {
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ import (
 | 
			
		||||
 | 
			
		||||
// Package-level errors.
 | 
			
		||||
var (
 | 
			
		||||
	ErrNotImplementedYet = errors.New("not implemented yet")
 | 
			
		||||
	ErrNotImplementedYet = errors.New("not implemented yet (sqlnext)")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func ProvideSQLResourceServer(db db.EntityDBInterface, tracer tracing.Tracer) (resource.ResourceServer, error) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user