/
plugincontext.go
159 lines (134 loc) · 5 KB
/
plugincontext.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package plugincontext
import (
"context"
"encoding/json"
"errors"
"fmt"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/plugins"
"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/user"
)
var ErrPluginNotFound = errors.New("plugin not found")
func ProvideService(cacheService *localcache.CacheService, pluginStore plugins.Store,
dataSourceService datasources.DataSourceService, pluginSettingsService pluginsettings.Service) *Provider {
return &Provider{
cacheService: cacheService,
pluginStore: pluginStore,
dataSourceService: dataSourceService,
pluginSettingsService: pluginSettingsService,
}
}
type Provider struct {
cacheService *localcache.CacheService
pluginStore plugins.Store
dataSourceService datasources.DataSourceService
pluginSettingsService pluginsettings.Service
}
// Get allows getting plugin context by its ID. If datasourceUID is not empty string
// then PluginContext.DataSourceInstanceSettings will be resolved and appended to
// returned context.
// Note: *user.SignedInUser can be nil.
func (p *Provider) Get(ctx context.Context, pluginID string, user *user.SignedInUser, orgID int64) (backend.PluginContext, error) {
plugin, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, ErrPluginNotFound
}
pCtx := backend.PluginContext{
PluginID: pluginID,
}
if user != nil {
pCtx.OrgID = user.OrgID
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
}
return pCtx, nil
}
// GetWithDataSource allows getting plugin context by its ID and PluginContext.DataSourceInstanceSettings will be
// resolved and appended to the returned context.
// Note: *user.SignedInUser can be nil.
func (p *Provider) GetWithDataSource(ctx context.Context, pluginID string, user *user.SignedInUser, ds *datasources.DataSource) (backend.PluginContext, error) {
_, exists := p.pluginStore.Plugin(ctx, pluginID)
if !exists {
return backend.PluginContext{}, ErrPluginNotFound
}
pCtx := backend.PluginContext{
PluginID: pluginID,
}
if user != nil {
pCtx.OrgID = user.OrgID
pCtx.User = adapters.BackendUserFromSignedInUser(user)
}
datasourceSettings, err := adapters.ModelToInstanceSettings(ds, p.decryptSecureJsonDataFn(ctx))
if err != nil {
return pCtx, err
}
pCtx.DataSourceInstanceSettings = datasourceSettings
return pCtx, nil
}
const pluginSettingsCacheTTL = 5 * time.Second
const pluginSettingsCachePrefix = "plugin-setting-"
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
}