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/services/pluginsintegration/plugincontext/plugincontext.go

241 lines
8.1 KiB

package plugincontext
import (
"context"
"encoding/json"
"errors"
"fmt"
"runtime"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/backend/useragent"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/envvars"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/adapters"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
"github.com/grafana/grafana/pkg/setting"
)
const (
pluginSettingsCacheTTL = 5 * time.Second
pluginSettingsCachePrefix = "plugin-setting-"
)
func ProvideService(cfg *setting.Cfg, cacheService *localcache.CacheService, pluginStore pluginstore.Store,
dataSourceCache datasources.CacheService, dataSourceService datasources.DataSourceService,
pluginSettingsService pluginsettings.Service, licensing plugins.Licensing, pCfg *config.Cfg) *Provider {
return &Provider{
cfg: cfg,
cacheService: cacheService,
pluginStore: pluginStore,
dataSourceCache: dataSourceCache,
dataSourceService: dataSourceService,
pluginSettingsService: pluginSettingsService,
pluginEnvVars: envvars.NewProvider(pCfg, licensing),
logger: log.New("plugin.context"),
}
}
type Provider struct {
cfg *setting.Cfg
pluginEnvVars *envvars.Service
cacheService *localcache.CacheService
pluginStore pluginstore.Store
dataSourceCache datasources.CacheService
dataSourceService datasources.DataSourceService
pluginSettingsService pluginsettings.Service
logger log.Logger
}
// Get will retrieve plugin context by the provided pluginID and orgID.
// This is intended to be used for app plugin requests.
// PluginContext.AppInstanceSettings will be resolved and appended to the returned context.
// Note: identity.Requester can be nil.
func (p *Provider) Get(ctx context.Context, pluginID string, user identity.Requester, orgID int64) (backend.PluginContext, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, plugins.ErrPluginNotRegistered
}
pCtx := backend.PluginContext{
PluginID: plugin.ID,
PluginVersion: plugin.Info.Version,
}
if user != nil && !user.IsNil() {
pCtx.OrgID = user.GetOrgID()
pCtx.User = adapters.BackendUserFromSignedInUser(user)
}
if plugin.IsApp() {
appSettings, err := p.appInstanceSettings(ctx, pluginID, orgID)
if err != nil {
return backend.PluginContext{}, err
}
pCtx.AppInstanceSettings = appSettings
}
settings := p.pluginEnvVars.GetConfigMap(ctx, pluginID, plugin.ExternalService)
pCtx.GrafanaConfig = backend.NewGrafanaCfg(settings)
ua, err := useragent.New(p.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH)
if err != nil {
p.logger.Warn("Could not create user agent", "error", err)
}
pCtx.UserAgent = ua
return pCtx, nil
}
// GetWithDataSource will retrieve plugin context by the provided pluginID and datasource.
// This is intended to be used for datasource plugin requests.
// PluginContext.DataSourceInstanceSettings will be resolved and appended to the returned context.
// Note: identity.Requester can be nil.
func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user identity.Requester, ds *datasources.DataSource) (backend.PluginContext, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, plugins.ErrPluginNotRegistered
}
pCtx := backend.PluginContext{
PluginID: plugin.ID,
PluginVersion: plugin.Info.Version,
}
if user != nil && !user.IsNil() {
pCtx.OrgID = user.GetOrgID()
pCtx.User = adapters.BackendUserFromSignedInUser(user)
}
datasourceSettings, err := adapters.ModelToInstanceSettings(ds, p.decryptSecureJsonDataFn(ctx))
if err != nil {
return pCtx, err
}
pCtx.DataSourceInstanceSettings = datasourceSettings
settings := p.pluginEnvVars.GetConfigMap(ctx, pluginID, plugin.ExternalService)
pCtx.GrafanaConfig = backend.NewGrafanaCfg(settings)
ua, err := useragent.New(p.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH)
if err != nil {
p.logger.Warn("Could not create user agent", "error", err)
}
pCtx.UserAgent = ua
return pCtx, nil
}
// PluginContextForDataSource will retrieve plugin context by the provided pluginID and datasource UID / K8s name.
// This is intended to be used for datasource API server plugin requests.
func (p *Provider) PluginContextForDataSource(ctx context.Context, pluginID, name string) (backend.PluginContext, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, plugins.ErrPluginNotRegistered
}
user, err := appcontext.User(ctx)
if err != nil {
return backend.PluginContext{}, err
}
ds, err := p.dataSourceCache.GetDatasourceByUID(ctx, name, user, false)
if err != nil {
return backend.PluginContext{}, err
}
pCtx := backend.PluginContext{
PluginID: plugin.ID,
PluginVersion: plugin.Info.Version,
}
if user != nil && !user.IsNil() {
pCtx.OrgID = user.GetOrgID()
pCtx.User = adapters.BackendUserFromSignedInUser(user)
}
datasourceSettings, err := adapters.ModelToInstanceSettings(ds, p.decryptSecureJsonDataFn(ctx))
if err != nil {
return pCtx, err
}
pCtx.DataSourceInstanceSettings = datasourceSettings
settings := p.pluginEnvVars.GetConfigMap(ctx, pluginID, plugin.ExternalService)
pCtx.GrafanaConfig = backend.NewGrafanaCfg(settings)
ua, err := useragent.New(p.cfg.BuildVersion, runtime.GOOS, runtime.GOARCH)
if err != nil {
p.logger.Warn("Could not create user agent", "error", err)
}
pCtx.UserAgent = ua
return pCtx, nil
}
func (p *Provider) appInstanceSettings(ctx context.Context, pluginID string, orgID int64) (*backend.AppInstanceSettings, error) {
jsonData := json.RawMessage{}
decryptedSecureJSONData := map[string]string{}
var updated time.Time
ps, err := p.getCachedPluginSettings(ctx, pluginID, orgID)
if err != nil {
// pluginsettings.ErrPluginSettingNotFound is expected if there's no row found for plugin setting in database (if non-app plugin).
// Otherwise, something is wrong with cache or database, and we return the error to the client.
if !errors.Is(err, pluginsettings.ErrPluginSettingNotFound) {
return nil, fmt.Errorf("%v: %w", "Failed to get plugin settings", err)
}
} else {
jsonData, err = json.Marshal(ps.JSONData)
if err != nil {
return nil, fmt.Errorf("%v: %w", "Failed to unmarshal plugin json data", err)
}
decryptedSecureJSONData = p.pluginSettingsService.DecryptedValues(ps)
updated = ps.Updated
}
return &backend.AppInstanceSettings{
JSONData: jsonData,
DecryptedSecureJSONData: decryptedSecureJSONData,
Updated: updated,
}, nil
}
func (p *Provider) InvalidateSettingsCache(_ context.Context, pluginID string) {
p.cacheService.Delete(getCacheKey(pluginID))
}
func (p *Provider) getCachedPluginSettings(ctx context.Context, pluginID string, orgID int64) (*pluginsettings.DTO, error) {
cacheKey := getCacheKey(pluginID)
if cached, found := p.cacheService.Get(cacheKey); found {
ps := cached.(*pluginsettings.DTO)
if ps.OrgID == orgID {
return ps, nil
}
}
ps, err := p.pluginSettingsService.GetPluginSettingByPluginID(ctx, &pluginsettings.GetByPluginIDArgs{
PluginID: pluginID,
OrgID: orgID,
})
if err != nil {
return nil, err
}
p.cacheService.Set(cacheKey, ps, pluginSettingsCacheTTL)
return ps, nil
}
func (p *Provider) decryptSecureJsonDataFn(ctx context.Context) func(ds *datasources.DataSource) (map[string]string, error) {
return func(ds *datasources.DataSource) (map[string]string, error) {
return p.dataSourceService.DecryptedValues(ctx, ds)
}
}
func getCacheKey(pluginID string) string {
return pluginSettingsCachePrefix + pluginID
}