Dashboard Versions: Make compatible with app platform (#99327)

This commit is contained in:
Stephanie Hingtgen
2025-01-28 07:17:52 -07:00
committed by GitHub
parent 05905a5069
commit 0cef2b9ae7
44 changed files with 887 additions and 413 deletions

View File

@ -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
}