Files
grafana/pkg/registry/apis/provisioning/resources/object.go
Roberto Jiménez Sánchez 7d630ec3b1 Provisioning: Refactor tweaks to support MT controllers (#110581)
* Refactor common code to support MT controllers

* Delete original status files
2025-09-04 10:06:50 +00:00

205 lines
5.7 KiB
Go

package resources
import (
"context"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
dashboard "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
)
// Get repository stats
//
//go:generate mockery --name ResourceLister --structname MockResourceLister --inpackage --filename resource_lister_mock.go --with-expecter
type ResourceLister interface {
List(ctx context.Context, namespace, repository string) (*provisioning.ResourceList, error)
Stats(ctx context.Context, namespace, repository string) (*provisioning.ResourceStats, error)
}
type ResourceStore interface {
resourcepb.ManagedObjectIndexClient
resourcepb.ResourceIndexClient
}
type ResourceListerFromSearch struct {
store ResourceStore
legacyMigrator legacy.LegacyMigrator
storageStatus dualwrite.Service
}
func NewResourceLister(store ResourceStore) ResourceLister {
return &ResourceListerFromSearch{store: store}
}
// FIXME: the logic about migration and storage should probably be separated from this
func NewResourceListerForMigrations(
store ResourceStore,
legacyMigrator legacy.LegacyMigrator,
storageStatus dualwrite.Service,
) ResourceLister {
return &ResourceListerFromSearch{
store: store,
legacyMigrator: legacyMigrator,
storageStatus: storageStatus,
}
}
// List implements ResourceLister.
func (o *ResourceListerFromSearch) List(ctx context.Context, namespace, repository string) (*provisioning.ResourceList, error) {
objects, err := o.store.ListManagedObjects(ctx, &resourcepb.ListManagedObjectsRequest{
Namespace: namespace,
Kind: string(utils.ManagerKindRepo),
Id: repository,
})
if err != nil {
return nil, err
}
if objects.Error != nil {
return nil, resource.GetError(objects.Error)
}
list := &provisioning.ResourceList{}
for _, v := range objects.Items {
list.Items = append(list.Items, provisioning.ResourceListItem{
Path: v.Path,
Group: v.Object.Group,
Resource: v.Object.Resource,
Name: v.Object.Name,
Hash: v.Hash,
Time: v.Time,
Title: v.Title,
Folder: v.Folder,
})
}
return list, nil
}
// Stats implements ResourceLister.
func (o *ResourceListerFromSearch) Stats(ctx context.Context, namespace, repository string) (*provisioning.ResourceStats, error) {
req := &resourcepb.CountManagedObjectsRequest{
Namespace: namespace,
}
if repository != "" {
req.Kind = string(utils.ManagerKindRepo)
req.Id = repository
}
counts, err := o.store.CountManagedObjects(ctx, req)
if err != nil {
return nil, err
}
if counts.Error != nil {
return nil, resource.GetError(counts.Error)
}
lookup := make(map[string]*provisioning.ManagerStats)
for _, v := range counts.Items {
key := v.Kind + ":" + v.Id
m := lookup[key]
if m == nil {
m = &provisioning.ManagerStats{
Kind: utils.ManagerKind(v.Kind),
Identity: v.Id,
}
lookup[key] = m
}
m.Stats = append(m.Stats, provisioning.ResourceCount{
Group: v.Group,
Resource: v.Resource,
Count: v.Count,
})
}
stats := &provisioning.ResourceStats{
TypeMeta: metav1.TypeMeta{
APIVersion: provisioning.SchemeGroupVersion.String(),
Kind: "ResourceStats",
},
}
for _, v := range lookup {
stats.Managed = append(stats.Managed, *v)
}
// When selecting an explicit repository, do not fetch global stats
if repository != "" {
return stats, nil
}
// Get the stats based on what a migration could support
if o.storageStatus != nil && o.legacyMigrator != nil && dualwrite.IsReadingLegacyDashboardsAndFolders(ctx, o.storageStatus) {
rsp, err := o.legacyMigrator.Migrate(ctx, legacy.MigrateOptions{
Namespace: namespace,
Resources: []schema.GroupResource{{
Group: dashboard.GROUP, Resource: dashboard.DASHBOARD_RESOURCE,
}, {
Group: folders.GROUP, Resource: folders.RESOURCE,
}},
WithHistory: false,
OnlyCount: true,
})
if err != nil {
return nil, err
}
for _, v := range rsp.Summary {
stats.Instance = append(stats.Instance, provisioning.ResourceCount{
Group: v.Group,
Resource: v.Resource,
Count: v.Count,
})
// Everything is unmanaged in legacy storage
stats.Unmanaged = append(stats.Unmanaged, provisioning.ResourceCount{
Group: v.Group,
Resource: v.Resource,
Count: v.Count,
})
}
return stats, nil
}
// Get full instance stats
info, err := o.store.GetStats(ctx, &resourcepb.ResourceStatsRequest{
Namespace: namespace,
})
if err != nil {
return nil, err
}
// Create a map to track managed counts by group/resource
managedCounts := make(map[string]int64)
for _, manager := range stats.Managed {
for _, managedStat := range manager.Stats {
key := managedStat.Group + ":" + managedStat.Resource
managedCounts[key] += managedStat.Count
}
}
for _, v := range info.Stats {
stats.Instance = append(stats.Instance, provisioning.ResourceCount{
Group: v.Group,
Resource: v.Resource,
Count: v.Count,
})
// Calculate unmanaged count: total - managed
key := v.Group + ":" + v.Resource
managedCount := managedCounts[key]
unmanagedCount := v.Count - managedCount
if unmanagedCount > 0 {
stats.Unmanaged = append(stats.Unmanaged, provisioning.ResourceCount{
Group: v.Group,
Resource: v.Resource,
Count: unmanagedCount,
})
}
}
return stats, nil
}