The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/registry/apis/provisioning/resources/client.go

216 lines
6.3 KiB

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
}