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/apps/plugins/pkg/app/app.go

168 lines
5.2 KiB

package app
import (
"context"
"fmt"
"sync"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/k8s"
appsdkapiserver "github.com/grafana/grafana-app-sdk/k8s/apiserver"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/operator"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
"k8s.io/apiserver/pkg/registry/rest"
restclient "k8s.io/client-go/rest"
pluginsappapis "github.com/grafana/grafana/apps/plugins/pkg/apis"
pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1"
"github.com/grafana/grafana/apps/plugins/pkg/app/install"
"github.com/grafana/grafana/apps/plugins/pkg/app/meta"
)
func New(cfg app.Config) (app.App, error) {
specificConfig, ok := cfg.SpecificConfig.(*PluginAppConfig)
if !ok {
return nil, fmt.Errorf("invalid config type")
}
metaKind := simple.AppManagedKind{
Kind: pluginsv0alpha1.MetaKind(),
}
pluginKind := simple.AppManagedKind{
Kind: pluginsv0alpha1.PluginKind(),
}
logger := logging.DefaultLogger.With("app", "plugins.app")
if specificConfig.EnableChildReconciler {
reconcilerLogger := logger.With("component", "reconciler.children")
clientGenerator := k8s.NewClientRegistry(cfg.KubeConfig, k8s.DefaultClientConfig())
registrar := install.NewInstallRegistrar(reconcilerLogger, clientGenerator)
pluginKind.Reconciler = install.NewChildPluginReconciler(reconcilerLogger, specificConfig.MetaProviderManager, registrar)
}
simpleConfig := simple.AppConfig{
Name: "plugins",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
InformerOptions: operator.InformerOptions{
ErrorHandler: func(ctx context.Context, err error) {
logger.Error("Child plugin informer failed", "error", err)
},
},
},
ManagedKinds: []simple.AppManagedKind{metaKind, pluginKind},
}
a, err := simple.NewApp(simpleConfig)
if err != nil {
return nil, err
}
err = a.ValidateManifest(cfg.ManifestData)
if err != nil {
return nil, err
}
// Register MetaProviderManager as a runnable so its cleanup goroutine is managed by the app lifecycle
a.AddRunnable(specificConfig.MetaProviderManager)
return a, nil
}
type PluginAppConfig struct {
MetaProviderManager *meta.ProviderManager
EnableChildReconciler bool
}
func NewPluginsAppInstaller(
logger logging.Logger,
authorizer authorizer.Authorizer,
metaProviderManager *meta.ProviderManager,
enableChildReconciler bool,
) (*PluginAppInstaller, error) {
specificConfig := &PluginAppConfig{
MetaProviderManager: metaProviderManager,
EnableChildReconciler: enableChildReconciler,
}
provider := simple.NewAppProvider(pluginsappapis.LocalManifest(), specificConfig, New)
appConfig := app.Config{
KubeConfig: restclient.Config{}, // this will be overridden by the installer's InitializeApp method
ManifestData: *pluginsappapis.LocalManifest().ManifestData,
SpecificConfig: specificConfig,
}
defaultInstaller, err := appsdkapiserver.NewDefaultAppInstaller(provider, appConfig, pluginsappapis.NewGoTypeAssociator())
if err != nil {
return nil, err
}
appInstaller := &PluginAppInstaller{
AppInstaller: defaultInstaller,
authorizer: authorizer,
metaManager: metaProviderManager,
logger: logger,
ready: make(chan struct{}),
}
return appInstaller, nil
}
type PluginAppInstaller struct {
appsdkapiserver.AppInstaller
metaManager *meta.ProviderManager
authorizer authorizer.Authorizer
logger logging.Logger
// restConfig is set during InitializeApp and used by the client factory
restConfig *restclient.Config
ready chan struct{}
readyOnce sync.Once
}
func (p *PluginAppInstaller) InitializeApp(restConfig restclient.Config) error {
if p.restConfig == nil {
p.restConfig = &restConfig
p.readyOnce.Do(func() {
close(p.ready)
})
}
return p.AppInstaller.InitializeApp(restConfig)
}
func (p *PluginAppInstaller) InstallAPIs(
server appsdkapiserver.GenericAPIServer,
restOptsGetter generic.RESTOptionsGetter,
) error {
// Create a client factory function that will be called lazily when the client is needed.
// This uses the rest config from the app, which is set during InitializeApp.
clientFactory := func(ctx context.Context) (*pluginsv0alpha1.PluginClient, error) {
<-p.ready
if p.restConfig == nil {
return nil, fmt.Errorf("rest config not yet initialized, app must be initialized before client can be created")
}
clientGenerator := k8s.NewClientRegistry(*p.restConfig, k8s.DefaultClientConfig())
client, err := pluginsv0alpha1.NewPluginClientFromGenerator(clientGenerator)
if err != nil {
return nil, fmt.Errorf("failed to create plugin client: %w", err)
}
return client, nil
}
pluginMetaGVR := pluginsv0alpha1.MetaKind().GroupVersionResource()
replacedStorage := map[schema.GroupVersionResource]rest.Storage{
pluginMetaGVR: NewMetaStorage(p.logger, p.metaManager, clientFactory),
}
wrappedServer := &customStorageWrapper{
wrapped: server,
replace: replacedStorage,
}
return p.AppInstaller.InstallAPIs(wrappedServer, restOptsGetter)
}
func (p *PluginAppInstaller) GetAuthorizer() authorizer.Authorizer {
return p.authorizer
}