mirror of
https://github.com/grafana/grafana.git
synced 2025-08-03 04:22:13 +08:00
Dashboard Versions: Make compatible with app platform (#99327)
This commit is contained in:

committed by
GitHub

parent
05905a5069
commit
0cef2b9ae7
@ -3,12 +3,26 @@ package dashverimpl
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/infra/db"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -17,19 +31,32 @@ const (
|
||||
)
|
||||
|
||||
type Service struct {
|
||||
cfg *setting.Cfg
|
||||
store store
|
||||
dashSvc dashboards.DashboardService
|
||||
log log.Logger
|
||||
cfg *setting.Cfg
|
||||
store store
|
||||
dashSvc dashboards.DashboardService
|
||||
k8sclient client.K8sHandler
|
||||
features featuremgmt.FeatureToggles
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.DashboardService) dashver.Service {
|
||||
func ProvideService(cfg *setting.Cfg, db db.DB, dashboardService dashboards.DashboardService, dashboardStore dashboards.Store, features featuremgmt.FeatureToggles,
|
||||
restConfigProvider apiserver.RestConfigProvider, userService user.Service, unified resource.ResourceClient) dashver.Service {
|
||||
return &Service{
|
||||
cfg: cfg,
|
||||
store: &sqlStore{
|
||||
db: db,
|
||||
dialect: db.GetDialect(),
|
||||
},
|
||||
features: features,
|
||||
k8sclient: client.NewK8sHandler(
|
||||
cfg,
|
||||
request.GetNamespaceMapper(cfg),
|
||||
v0alpha1.DashboardResourceInfo.GroupVersionResource(),
|
||||
restConfigProvider,
|
||||
unified,
|
||||
dashboardStore,
|
||||
userService,
|
||||
),
|
||||
dashSvc: dashboardService,
|
||||
log: log.New("dashboard-version"),
|
||||
}
|
||||
@ -49,13 +76,21 @@ func (s *Service) Get(ctx context.Context, query *dashver.GetDashboardVersionQue
|
||||
// versions table, at time of this writing), so get the DashboardID if it
|
||||
// was not populated.
|
||||
if query.DashboardID == 0 {
|
||||
id, err := s.getDashIDMaybeEmpty(ctx, query.DashboardUID)
|
||||
id, err := s.getDashIDMaybeEmpty(ctx, query.DashboardUID, query.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
query.DashboardID = id
|
||||
}
|
||||
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
version, err := s.getHistoryThroughK8s(ctx, query.OrgID, query.DashboardUID, query.Version)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return version, nil
|
||||
}
|
||||
|
||||
version, err := s.store.Get(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -95,7 +130,7 @@ func (s *Service) DeleteExpired(ctx context.Context, cmd *dashver.DeleteExpiredV
|
||||
}
|
||||
|
||||
// List all dashboard versions for the given dashboard ID.
|
||||
func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersionsQuery) ([]*dashver.DashboardVersionDTO, error) {
|
||||
func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersionsQuery) (*dashver.DashboardVersionResponse, error) {
|
||||
// Get the DashboardUID if not populated
|
||||
if query.DashboardUID == "" {
|
||||
u, err := s.getDashUIDMaybeEmpty(ctx, query.DashboardID)
|
||||
@ -109,7 +144,7 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
|
||||
// versions table, at time of this writing), so get the DashboardID if it
|
||||
// was not populated.
|
||||
if query.DashboardID == 0 {
|
||||
id, err := s.getDashIDMaybeEmpty(ctx, query.DashboardUID)
|
||||
id, err := s.getDashIDMaybeEmpty(ctx, query.DashboardUID, query.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -118,6 +153,21 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
|
||||
if query.Limit == 0 {
|
||||
query.Limit = 1000
|
||||
}
|
||||
|
||||
if s.features.IsEnabledGlobally(featuremgmt.FlagKubernetesCliDashboards) {
|
||||
versions, err := s.listHistoryThroughK8s(
|
||||
ctx,
|
||||
query.OrgID,
|
||||
query.DashboardUID,
|
||||
int64(query.Limit),
|
||||
query.ContinueToken,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return versions, nil
|
||||
}
|
||||
|
||||
dvs, err := s.store.List(ctx, query)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -126,7 +176,9 @@ func (s *Service) List(ctx context.Context, query *dashver.ListDashboardVersions
|
||||
for i, v := range dvs {
|
||||
dtos[i] = v.ToDTO(query.DashboardUID)
|
||||
}
|
||||
return dtos, nil
|
||||
return &dashver.DashboardVersionResponse{
|
||||
Versions: dtos,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getDashUIDMaybeEmpty is a helper function which takes a dashboardID and
|
||||
@ -149,8 +201,8 @@ func (s *Service) getDashUIDMaybeEmpty(ctx context.Context, id int64) (string, e
|
||||
|
||||
// getDashIDMaybeEmpty is a helper function which takes a dashboardUID and
|
||||
// returns the ID. If the dashboard is not found, it will return -1.
|
||||
func (s *Service) getDashIDMaybeEmpty(ctx context.Context, uid string) (int64, error) {
|
||||
q := dashboards.GetDashboardQuery{UID: uid}
|
||||
func (s *Service) getDashIDMaybeEmpty(ctx context.Context, uid string, orgID int64) (int64, error) {
|
||||
q := dashboards.GetDashboardQuery{UID: uid, OrgID: orgID}
|
||||
result, err := s.dashSvc.GetDashboard(ctx, &q)
|
||||
if err != nil {
|
||||
if errors.Is(err, dashboards.ErrDashboardNotFound) {
|
||||
@ -163,3 +215,116 @@ func (s *Service) getDashIDMaybeEmpty(ctx context.Context, uid string) (int64, e
|
||||
}
|
||||
return result.ID, nil
|
||||
}
|
||||
|
||||
func (s *Service) getHistoryThroughK8s(ctx context.Context, orgID int64, dashboardUID string, rv int64) (*dashver.DashboardVersionDTO, error) {
|
||||
out, err := s.k8sclient.Get(ctx, dashboardUID, orgID, v1.GetOptions{ResourceVersion: strconv.FormatInt(rv, 10)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if out == nil {
|
||||
return nil, dashboards.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
dash, err := s.UnstructuredToLegacyDashboardVersion(ctx, out, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dash, nil
|
||||
}
|
||||
|
||||
func (s *Service) listHistoryThroughK8s(ctx context.Context, orgID int64, dashboardUID string, limit int64, continueToken string) (*dashver.DashboardVersionResponse, error) {
|
||||
out, err := s.k8sclient.List(ctx, orgID, v1.ListOptions{
|
||||
LabelSelector: utils.LabelKeyGetHistory + "=" + dashboardUID,
|
||||
Limit: limit,
|
||||
Continue: continueToken,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else if out == nil {
|
||||
return nil, dashboards.ErrDashboardNotFound
|
||||
}
|
||||
|
||||
dashboards := make([]*dashver.DashboardVersionDTO, len(out.Items))
|
||||
for i, item := range out.Items {
|
||||
dash, err := s.UnstructuredToLegacyDashboardVersion(ctx, &item, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dashboards[i] = dash
|
||||
}
|
||||
|
||||
return &dashver.DashboardVersionResponse{
|
||||
ContinueToken: out.GetContinue(),
|
||||
Versions: dashboards,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *Service) UnstructuredToLegacyDashboardVersion(ctx context.Context, item *unstructured.Unstructured, orgID int64) (*dashver.DashboardVersionDTO, error) {
|
||||
spec, ok := item.Object["spec"].(map[string]any)
|
||||
if !ok {
|
||||
return nil, errors.New("error parsing dashboard from k8s response")
|
||||
}
|
||||
obj, err := utils.MetaAccessor(item)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
uid := obj.GetName()
|
||||
spec["uid"] = uid
|
||||
|
||||
dashVersion := 0
|
||||
parentVersion := 0
|
||||
if version, ok := spec["version"].(int64); ok {
|
||||
dashVersion = int(version)
|
||||
parentVersion = dashVersion - 1
|
||||
}
|
||||
|
||||
createdBy, err := s.k8sclient.GetUserFromMeta(ctx, obj.GetCreatedBy())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
id, err := obj.GetResourceVersionInt64()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
restoreVer, err := getRestoreVersion(obj.GetMessage())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
out := dashver.DashboardVersionDTO{
|
||||
ID: id,
|
||||
DashboardID: obj.GetDeprecatedInternalID(), // nolint:staticcheck
|
||||
DashboardUID: uid,
|
||||
Created: obj.GetCreationTimestamp().Time,
|
||||
CreatedBy: createdBy.ID,
|
||||
Message: obj.GetMessage(),
|
||||
RestoredFrom: restoreVer,
|
||||
Version: dashVersion,
|
||||
ParentVersion: parentVersion,
|
||||
Data: simplejson.NewFromAny(spec),
|
||||
}
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
var restoreMsg = "Restored from version "
|
||||
|
||||
func DashboardRestoreMessage(version int) string {
|
||||
return fmt.Sprintf("%s%d", restoreMsg, version)
|
||||
}
|
||||
|
||||
func getRestoreVersion(msg string) (int, error) {
|
||||
parts := strings.Split(msg, restoreMsg)
|
||||
if len(parts) < 2 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
ver, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return int(ver), nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user