package datasource import ( "context" "fmt" "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana/pkg/apimachinery/identity" datasourceV0 "github.com/grafana/grafana/pkg/apis/datasource/v0alpha1" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext" "github.com/grafana/grafana/pkg/setting" ) // This provides access to settings saved in the database. // Authorization checks will happen within each function, and the user in ctx will // limit which namespace/tenant/org we are talking to type PluginDatasourceProvider interface { // Get a single data source (any type) GetDataSource(ctx context.Context, uid string) (*datasourceV0.DataSource, error) // List all datasources (any type) ListDataSources(ctx context.Context) (*datasourceV0.DataSourceList, error) // Create a data source CreateDataSource(ctx context.Context, ds *datasourceV0.DataSource) (*datasourceV0.DataSource, error) // Update a data source UpdateDataSource(ctx context.Context, ds *datasourceV0.DataSource) (*datasourceV0.DataSource, error) // Delete a data source (any type) DeleteDataSource(ctx context.Context, uid string) error // Return settings (decrypted!) for a specific plugin // This will require "query" permission for the user in context GetInstanceSettings(ctx context.Context, uid string) (*backend.DataSourceInstanceSettings, error) } type ScopedPluginDatasourceProvider interface { GetDatasourceProvider(pluginJson plugins.JSONData) PluginDatasourceProvider } // PluginContext requires adding system settings (feature flags, etc) to the datasource config type PluginContextWrapper interface { PluginContextForDataSource(ctx context.Context, datasourceSettings *backend.DataSourceInstanceSettings) (backend.PluginContext, error) } func ProvideDefaultPluginConfigs( dsService datasources.DataSourceService, dsCache datasources.CacheService, contextProvider *plugincontext.Provider, cfg *setting.Cfg, ) ScopedPluginDatasourceProvider { return &cachingDatasourceProvider{ dsService: dsService, dsCache: dsCache, contextProvider: contextProvider, converter: &converter{ mapper: request.GetNamespaceMapper(cfg), }, } } type cachingDatasourceProvider struct { dsService datasources.DataSourceService dsCache datasources.CacheService contextProvider *plugincontext.Provider converter *converter } func (q *cachingDatasourceProvider) GetDatasourceProvider(pluginJson plugins.JSONData) PluginDatasourceProvider { group, _ := plugins.GetDatasourceGroupNameFromPluginID(pluginJson.ID) return &scopedDatasourceProvider{ plugin: pluginJson, dsService: q.dsService, dsCache: q.dsCache, contextProvider: q.contextProvider, converter: &converter{ mapper: q.converter.mapper, plugin: pluginJson.ID, alias: pluginJson.AliasIDs, group: group, }, } } type scopedDatasourceProvider struct { plugin plugins.JSONData dsService datasources.DataSourceService dsCache datasources.CacheService contextProvider *plugincontext.Provider converter *converter } var ( _ PluginDatasourceProvider = (*scopedDatasourceProvider)(nil) _ ScopedPluginDatasourceProvider = (*cachingDatasourceProvider)(nil) ) func (q *scopedDatasourceProvider) GetInstanceSettings(ctx context.Context, uid string) (*backend.DataSourceInstanceSettings, error) { if q.contextProvider == nil { return nil, fmt.Errorf("missing contextProvider") } return q.contextProvider.GetDataSourceInstanceSettings(ctx, uid) } // CreateDataSource implements PluginDatasourceProvider. func (q *scopedDatasourceProvider) CreateDataSource(ctx context.Context, ds *datasourceV0.DataSource) (*datasourceV0.DataSource, error) { cmd, err := q.converter.toAddCommand(ds) if err != nil { return nil, err } out, err := q.dsService.AddDataSource(ctx, cmd) if err != nil { return nil, err } return q.converter.asDataSource(out) } // UpdateDataSource implements PluginDatasourceProvider. func (q *scopedDatasourceProvider) UpdateDataSource(ctx context.Context, ds *datasourceV0.DataSource) (*datasourceV0.DataSource, error) { cmd, err := q.converter.toUpdateCommand(ds) if err != nil { return nil, err } out, err := q.dsService.UpdateDataSource(ctx, cmd) if err != nil { return nil, err } return q.converter.asDataSource(out) } // Delete implements PluginDatasourceProvider. func (q *scopedDatasourceProvider) DeleteDataSource(ctx context.Context, uid string) error { user, err := identity.GetRequester(ctx) if err != nil { return err } ds, err := q.dsCache.GetDatasourceByUID(ctx, uid, user, false) if err != nil { return err } if ds == nil { return fmt.Errorf("not found") } return q.dsService.DeleteDataSource(ctx, &datasources.DeleteDataSourceCommand{ ID: ds.ID, UID: ds.UID, OrgID: ds.OrgID, Name: ds.Name, }) } // GetDataSource implements PluginDatasourceProvider. func (q *scopedDatasourceProvider) GetDataSource(ctx context.Context, uid string) (*datasourceV0.DataSource, error) { user, err := identity.GetRequester(ctx) if err != nil { return nil, err } ds, err := q.dsCache.GetDatasourceByUID(ctx, uid, user, false) if err != nil { return nil, err } return q.converter.asDataSource(ds) } // ListDataSource implements PluginDatasourceProvider. func (q *scopedDatasourceProvider) ListDataSources(ctx context.Context) (*datasourceV0.DataSourceList, error) { info, err := request.NamespaceInfoFrom(ctx, true) if err != nil { return nil, err } dss, err := q.dsService.GetDataSourcesByType(ctx, &datasources.GetDataSourcesByTypeQuery{ OrgID: info.OrgID, Type: q.plugin.ID, AliasIDs: q.plugin.AliasIDs, }) if err != nil { return nil, err } result := &datasourceV0.DataSourceList{ Items: []datasourceV0.DataSource{}, } for _, ds := range dss { v, _ := q.converter.asDataSource(ds) result.Items = append(result.Items, *v) } return result, nil }