mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 05:42:31 +08:00
217 lines
6.3 KiB
Go
217 lines
6.3 KiB
Go
package resources
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/client-go/dynamic"
|
|
|
|
dashboardV1 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v1beta1"
|
|
dashboardV2 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1"
|
|
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
|
|
iam "github.com/grafana/grafana/pkg/apis/iam/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/services/apiserver"
|
|
"github.com/grafana/grafana/pkg/services/apiserver/client"
|
|
)
|
|
|
|
var (
|
|
UserResource = iam.UserResourceInfo.GroupVersionResource()
|
|
FolderResource = folders.FolderResourceInfo.GroupVersionResource()
|
|
DashboardResource = dashboardV1.DashboardResourceInfo.GroupVersionResource()
|
|
DashboardResourceV2 = dashboardV2.DashboardResourceInfo.GroupVersionResource()
|
|
|
|
// SupportedProvisioningResources is the list of resources that can fully managed from the UI
|
|
SupportedProvisioningResources = []schema.GroupVersionResource{FolderResource, DashboardResource}
|
|
|
|
// SupportsFolderAnnotation is the list of resources that can be saved in a folder
|
|
SupportsFolderAnnotation = []schema.GroupResource{FolderResource.GroupResource(), DashboardResource.GroupResource()}
|
|
)
|
|
|
|
// ClientFactory is a factory for creating clients for a given namespace
|
|
//
|
|
//go:generate mockery --name ClientFactory --structname MockClientFactory --inpackage --filename client_factory_mock.go --with-expecter
|
|
type ClientFactory interface {
|
|
Clients(ctx context.Context, namespace string) (ResourceClients, error)
|
|
}
|
|
|
|
type clientFactory struct {
|
|
configProvider apiserver.RestConfigProvider
|
|
}
|
|
|
|
// TODO: Rename to NamespacedClients
|
|
// ResourceClients provides access to clients within a namespace
|
|
//
|
|
//go:generate mockery --name ResourceClients --structname MockResourceClients --inpackage --filename clients_mock.go --with-expecter
|
|
type ResourceClients interface {
|
|
ForKind(gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error)
|
|
ForResource(gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error)
|
|
|
|
Folder() (dynamic.ResourceInterface, error)
|
|
User() (dynamic.ResourceInterface, error)
|
|
}
|
|
|
|
func NewClientFactory(configProvider apiserver.RestConfigProvider) ClientFactory {
|
|
return &clientFactory{configProvider}
|
|
}
|
|
|
|
func (f *clientFactory) Clients(ctx context.Context, namespace string) (ResourceClients, error) {
|
|
restConfig, err := f.configProvider.GetRestConfig(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if namespace == "" {
|
|
return nil, fmt.Errorf("missing namespace")
|
|
}
|
|
|
|
discovery, err := client.NewDiscoveryClient(restConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
client, err := dynamic.NewForConfig(restConfig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &resourceClients{
|
|
namespace: namespace,
|
|
discovery: discovery,
|
|
dynamic: client,
|
|
byKind: make(map[schema.GroupVersionKind]*clientInfo),
|
|
byResource: make(map[schema.GroupVersionResource]*clientInfo),
|
|
}, nil
|
|
}
|
|
|
|
type resourceClients struct {
|
|
namespace string
|
|
|
|
dynamic dynamic.Interface
|
|
discovery client.DiscoveryClient
|
|
|
|
// ResourceInterface cache for this context + namespace
|
|
mutex sync.Mutex
|
|
byKind map[schema.GroupVersionKind]*clientInfo
|
|
byResource map[schema.GroupVersionResource]*clientInfo
|
|
}
|
|
|
|
type clientInfo struct {
|
|
gvk schema.GroupVersionKind
|
|
gvr schema.GroupVersionResource
|
|
client dynamic.ResourceInterface
|
|
}
|
|
|
|
func (c *resourceClients) ForKind(gvk schema.GroupVersionKind) (dynamic.ResourceInterface, schema.GroupVersionResource, error) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
info, ok := c.byKind[gvk]
|
|
if ok && info.client != nil {
|
|
return info.client, info.gvr, nil
|
|
}
|
|
|
|
gvr, err := c.discovery.GetResourceForKind(gvk)
|
|
if err != nil {
|
|
return nil, schema.GroupVersionResource{}, err
|
|
}
|
|
info = &clientInfo{
|
|
gvk: gvk,
|
|
gvr: gvr,
|
|
client: c.dynamic.Resource(gvr).Namespace(c.namespace),
|
|
}
|
|
c.byKind[gvk] = info
|
|
c.byResource[gvr] = info
|
|
return info.client, info.gvr, nil
|
|
}
|
|
|
|
// ForResource returns a client for a resource.
|
|
// If the resource has a version, it will be used.
|
|
// If the resource does not have a version, the preferred version will be used.
|
|
func (c *resourceClients) ForResource(gvr schema.GroupVersionResource) (dynamic.ResourceInterface, schema.GroupVersionKind, error) {
|
|
c.mutex.Lock()
|
|
defer c.mutex.Unlock()
|
|
|
|
info, ok := c.byResource[gvr]
|
|
if ok && info.client != nil {
|
|
return info.client, info.gvk, nil
|
|
}
|
|
|
|
var err error
|
|
var gvk schema.GroupVersionKind
|
|
var versionless schema.GroupVersionResource
|
|
if gvr.Version == "" {
|
|
versionless = gvr
|
|
gvr, gvk, err = c.discovery.GetPreferredVesion(schema.GroupResource{
|
|
Group: gvr.Group,
|
|
Resource: gvr.Resource,
|
|
})
|
|
if err != nil {
|
|
return nil, schema.GroupVersionKind{}, err
|
|
}
|
|
|
|
info, ok := c.byResource[gvr]
|
|
if ok && info.client != nil {
|
|
c.byResource[versionless] = info
|
|
return info.client, info.gvk, nil
|
|
}
|
|
} else {
|
|
gvk, err = c.discovery.GetKindForResource(gvr)
|
|
if err != nil {
|
|
return nil, schema.GroupVersionKind{}, err
|
|
}
|
|
}
|
|
info = &clientInfo{
|
|
gvk: gvk,
|
|
gvr: gvr,
|
|
client: c.dynamic.Resource(gvr).Namespace(c.namespace),
|
|
}
|
|
c.byKind[gvk] = info
|
|
c.byResource[gvr] = info
|
|
if versionless.Group != "" {
|
|
c.byResource[versionless] = info
|
|
}
|
|
return info.client, info.gvk, nil
|
|
}
|
|
|
|
func (c *resourceClients) Folder() (dynamic.ResourceInterface, error) {
|
|
client, _, err := c.ForResource(FolderResource)
|
|
return client, err
|
|
}
|
|
|
|
func (c *resourceClients) User() (dynamic.ResourceInterface, error) {
|
|
v, _, err := c.ForResource(UserResource)
|
|
return v, err
|
|
}
|
|
|
|
// ForEach applies the function to each resource returned from the list operation
|
|
func ForEach(ctx context.Context, client dynamic.ResourceInterface, fn func(item *unstructured.Unstructured) error) error {
|
|
var continueToken string
|
|
for ctx.Err() == nil {
|
|
list, err := client.List(ctx, metav1.ListOptions{Limit: 100, Continue: continueToken})
|
|
if err != nil {
|
|
return fmt.Errorf("error executing list: %w", err)
|
|
}
|
|
|
|
for _, item := range list.Items {
|
|
if ctx.Err() != nil {
|
|
return ctx.Err()
|
|
}
|
|
|
|
if err := fn(&item); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
continueToken = list.GetContinue()
|
|
if continueToken == "" {
|
|
break
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|