Plugins: Move alias support to plugin json (but still hardcoded) (#75129)

This commit is contained in:
Ryan McKinley
2023-09-29 08:20:37 -07:00
committed by GitHub
parent e45867c635
commit 010b2461b9
16 changed files with 90 additions and 53 deletions

View File

@ -58,7 +58,7 @@ export interface PluginMeta<T extends KeyValue = {}> {
info: PluginMetaInfo; info: PluginMetaInfo;
includes?: PluginInclude[]; includes?: PluginInclude[];
state?: PluginState; state?: PluginState;
alias?: string; aliasIDs?: string[];
// System.load & relative URLS // System.load & relative URLS
module: string; module: string;

View File

@ -19,6 +19,7 @@ import (
"github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb/grafanads" "github.com/grafana/grafana/pkg/tsdb/grafanads"
"github.com/grafana/grafana/pkg/util" "github.com/grafana/grafana/pkg/util"
"golang.org/x/exp/slices"
) )
func (hs *HTTPServer) GetFrontendSettings(c *contextmodel.ReqContext) { func (hs *HTTPServer) GetFrontendSettings(c *contextmodel.ReqContext) {
@ -72,7 +73,7 @@ func (hs *HTTPServer) getFrontendSettings(c *contextmodel.ReqContext) (*dtos.Fro
panels[panel.ID] = plugins.PanelDTO{ panels[panel.ID] = plugins.PanelDTO{
ID: panel.ID, ID: panel.ID,
Name: panel.Name, Name: panel.Name,
Alias: panel.Alias, AliasIDs: panel.AliasIDs,
Info: panel.Info, Info: panel.Info,
Module: panel.Module, Module: panel.Module,
BaseURL: panel.BaseURL, BaseURL: panel.BaseURL,
@ -500,7 +501,7 @@ func (ap AvailablePlugins) Get(pluginType plugins.Type, pluginID string) (*avail
return p, true return p, true
} }
for _, p = range ap[pluginType] { for _, p = range ap[pluginType] {
if p.Plugin.ID == pluginID || p.Plugin.Alias == pluginID { if p.Plugin.ID == pluginID || slices.Contains(p.Plugin.AliasIDs, pluginID) {
return p, true return p, true
} }
} }

View File

@ -29,7 +29,6 @@ func DefaultConstructFunc(signatureCalculator plugins.SignatureCalculator, asset
// DefaultDecorateFuncs are the default DecorateFuncs used for the Decorate step of the Bootstrap stage. // DefaultDecorateFuncs are the default DecorateFuncs used for the Decorate step of the Bootstrap stage.
func DefaultDecorateFuncs(cfg *config.Cfg) []DecorateFunc { func DefaultDecorateFuncs(cfg *config.Cfg) []DecorateFunc {
return []DecorateFunc{ return []DecorateFunc{
AliasDecorateFunc,
AppDefaultNavURLDecorateFunc, AppDefaultNavURLDecorateFunc,
AppChildDecorateFunc(cfg), AppChildDecorateFunc(cfg),
} }
@ -79,19 +78,6 @@ func (c *DefaultConstructor) Construct(ctx context.Context, src plugins.PluginSo
return res, nil return res, nil
} }
// AliasDecorateFunc is a DecorateFunc that sets the alias for the plugin.
func AliasDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
switch p.ID {
case "grafana-pyroscope-datasource": // rebranding
p.Alias = "phlare"
case "grafana-testdata-datasource":
p.Alias = "testdata"
case "debug": // panel plugin used for testing
p.Alias = "debugX"
}
return p, nil
}
// AppDefaultNavURLDecorateFunc is a DecorateFunc that sets the default nav URL for app plugins. // AppDefaultNavURLDecorateFunc is a DecorateFunc that sets the default nav URL for app plugins.
func AppDefaultNavURLDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) { func AppDefaultNavURLDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
if p.IsApp() { if p.IsApp() {

View File

@ -48,8 +48,8 @@ func (i *InMemory) Add(_ context.Context, p *plugins.Plugin) error {
i.mu.Lock() i.mu.Lock()
i.store[p.ID] = p i.store[p.ID] = p
if p.Alias != "" { for _, a := range p.AliasIDs {
i.alias[p.Alias] = p i.alias[a] = p
} }
i.mu.Unlock() i.mu.Unlock()
@ -64,8 +64,10 @@ func (i *InMemory) Remove(_ context.Context, pluginID string) error {
i.mu.Lock() i.mu.Lock()
delete(i.store, pluginID) delete(i.store, pluginID)
if p != nil && p.Alias != "" { if p != nil {
delete(i.alias, p.Alias) for _, a := range p.AliasIDs {
delete(i.alias, a)
}
} }
i.mu.Unlock() i.mu.Unlock()

View File

@ -278,6 +278,7 @@ func TestAliasSupport(t *testing.T) {
pluginIdNew := "plugin-new" pluginIdNew := "plugin-new"
pluginIdOld := "plugin-old" pluginIdOld := "plugin-old"
pluginIdOld2 := "plugin-old2"
p, exists := i.Plugin(ctx, pluginIdNew) p, exists := i.Plugin(ctx, pluginIdNew)
require.False(t, exists) require.False(t, exists)
@ -285,9 +286,9 @@ func TestAliasSupport(t *testing.T) {
pluginNew := &plugins.Plugin{ pluginNew := &plugins.Plugin{
JSONData: plugins.JSONData{ JSONData: plugins.JSONData{
ID: pluginIdNew, ID: pluginIdNew,
AliasIDs: []string{pluginIdOld, pluginIdOld2},
}, },
Alias: pluginIdOld, // TODO: move to JSONData
} }
err := i.Add(ctx, pluginNew) err := i.Add(ctx, pluginNew)
require.NoError(t, err) require.NoError(t, err)
@ -302,6 +303,11 @@ func TestAliasSupport(t *testing.T) {
require.True(t, exists) require.True(t, exists)
require.Equal(t, pluginNew, found) require.Equal(t, pluginNew, found)
// Can lookup by the other old ID
found, exists = i.Plugin(ctx, pluginIdOld2)
require.True(t, exists)
require.Equal(t, pluginNew, found)
// Register the old plugin and look it up // Register the old plugin and look it up
pluginOld := &plugins.Plugin{JSONData: plugins.JSONData{ pluginOld := &plugins.Plugin{JSONData: plugins.JSONData{
ID: pluginIdOld, ID: pluginIdOld,

View File

@ -243,18 +243,18 @@ type DataSourceDTO struct {
} }
type PanelDTO struct { type PanelDTO struct {
ID string `json:"id"` ID string `json:"id"`
Name string `json:"name"` Name string `json:"name"`
Alias string `json:"alias,omitempty"` AliasIDs []string `json:"aliasIds,omitempty"`
Info Info `json:"info"` Info Info `json:"info"`
HideFromList bool `json:"hideFromList"` HideFromList bool `json:"hideFromList"`
Sort int `json:"sort"` Sort int `json:"sort"`
SkipDataQuery bool `json:"skipDataQuery"` SkipDataQuery bool `json:"skipDataQuery"`
ReleaseState string `json:"state"` ReleaseState string `json:"state"`
BaseURL string `json:"baseUrl"` BaseURL string `json:"baseUrl"`
Signature string `json:"signature"` Signature string `json:"signature"`
Module string `json:"module"` Module string `json:"module"`
AngularDetected bool `json:"angularDetected"` AngularDetected bool `json:"angularDetected"`
} }
type AppDTO struct { type AppDTO struct {

View File

@ -18,6 +18,10 @@ schemas: [{
id: string & strings.MinRunes(1) id: string & strings.MinRunes(1)
id: =~"^([0-9a-z]+\\-([0-9a-z]+\\-)?(\(strings.Join([ for t in _types {t}], "|"))))|(alertGroups|alertlist|annolist|barchart|bargauge|candlestick|canvas|dashlist|debug|datagrid|gauge|geomap|gettingstarted|graph|heatmap|histogram|icon|live|logs|news|nodeGraph|piechart|pluginlist|stat|state-timeline|status-history|table|table-old|text|timeseries|trend|traces|welcome|xychart|alertmanager|cloudwatch|dashboard|elasticsearch|grafana|grafana-azure-monitor-datasource|graphite|influxdb|jaeger|loki|mixed|mssql|mysql|opentsdb|postgres|prometheus|stackdriver|tempo|grafana-testdata-datasource|zipkin|phlare|parca)$" id: =~"^([0-9a-z]+\\-([0-9a-z]+\\-)?(\(strings.Join([ for t in _types {t}], "|"))))|(alertGroups|alertlist|annolist|barchart|bargauge|candlestick|canvas|dashlist|debug|datagrid|gauge|geomap|gettingstarted|graph|heatmap|histogram|icon|live|logs|news|nodeGraph|piechart|pluginlist|stat|state-timeline|status-history|table|table-old|text|timeseries|trend|traces|welcome|xychart|alertmanager|cloudwatch|dashboard|elasticsearch|grafana|grafana-azure-monitor-datasource|graphite|influxdb|jaeger|loki|mixed|mssql|mysql|opentsdb|postgres|prometheus|stackdriver|tempo|grafana-testdata-datasource|zipkin|phlare|parca)$"
// An alias is useful when migrating from one plugin id to another (rebranding etc)
// This should be used sparingly, and is currently only supported though a hardcoded checklist
aliasIDs?: [...string]
// Human-readable name of the plugin that is shown to the user in // Human-readable name of the plugin that is shown to the user in
// the UI. // the UI.
name: string name: string

View File

@ -278,6 +278,10 @@ type PluginDef struct {
// For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`. // For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`.
Alerting *bool `json:"alerting,omitempty"` Alerting *bool `json:"alerting,omitempty"`
// An alias is useful when migrating from one plugin id to another (rebranding etc)
// This should be used sparingly, and is currently only supported though a hardcoded checklist
AliasIDs []string `json:"aliasIDs,omitempty"`
// For data source plugins, if the plugin supports annotation // For data source plugins, if the plugin supports annotation
// queries. // queries.
Annotations *bool `json:"annotations,omitempty"` Annotations *bool `json:"annotations,omitempty"`

View File

@ -29,6 +29,7 @@ var (
ErrPluginFileRead = errors.New("file could not be read") ErrPluginFileRead = errors.New("file could not be read")
ErrUninstallInvalidPluginDir = errors.New("cannot recognize as plugin folder") ErrUninstallInvalidPluginDir = errors.New("cannot recognize as plugin folder")
ErrInvalidPluginJSON = errors.New("did not find valid type or id properties in plugin.json") ErrInvalidPluginJSON = errors.New("did not find valid type or id properties in plugin.json")
ErrUnsupportedAlias = errors.New("can not set alias in plugin.json")
) )
type Plugin struct { type Plugin struct {
@ -64,9 +65,6 @@ type Plugin struct {
log log.Logger log log.Logger
mu sync.Mutex mu sync.Mutex
// This will be moved to plugin.json when we have general support in gcom
Alias string `json:"alias,omitempty"`
} }
// JSONData represents the plugin's plugin.json // JSONData represents the plugin's plugin.json
@ -75,7 +73,7 @@ type JSONData struct {
ID string `json:"id"` ID string `json:"id"`
Type Type `json:"type"` Type Type `json:"type"`
Name string `json:"name"` Name string `json:"name"`
Alias string `json:"alias,omitempty"` AliasIDs []string `json:"aliasIDs,omitempty"`
Info Info `json:"info"` Info Info `json:"info"`
Dependencies Dependencies `json:"dependencies"` Dependencies Dependencies `json:"dependencies"`
Includes []*Includes `json:"includes"` Includes []*Includes `json:"includes"`
@ -130,12 +128,20 @@ func ReadPluginJSON(reader io.Reader) (JSONData, error) {
switch plugin.ID { switch plugin.ID {
case "grafana-piechart-panel": case "grafana-piechart-panel":
plugin.Name = "Pie Chart (old)" plugin.Name = "Pie Chart (old)"
case "grafana-pyroscope-datasource": // rebranding case "grafana-pyroscope-datasource":
plugin.Alias = "phlare" fallthrough
case "grafana-testdata-datasource": case "grafana-testdata-datasource":
plugin.Alias = "testdata" fallthrough
case "debug": // panel plugin used for testing case "annolist":
plugin.Alias = "debugX" fallthrough
case "debug":
if len(plugin.AliasIDs) == 0 {
return plugin, fmt.Errorf("expected alias to be set")
}
default: // TODO: when gcom validates the alias, this condition can be removed
if len(plugin.AliasIDs) > 0 {
return plugin, ErrUnsupportedAlias
}
} }
if len(plugin.Dependencies.Plugins) == 0 { if len(plugin.Dependencies.Plugins) == 0 {

View File

@ -107,14 +107,15 @@ func Test_ReadPluginJSON(t *testing.T) {
pluginJSON: func(t *testing.T) io.ReadCloser { pluginJSON: func(t *testing.T) io.ReadCloser {
pJSON := `{ pJSON := `{
"id": "grafana-pyroscope-datasource", "id": "grafana-pyroscope-datasource",
"type": "datasource" "type": "datasource",
"aliasIDs": ["phlare"]
}` }`
return io.NopCloser(strings.NewReader(pJSON)) return io.NopCloser(strings.NewReader(pJSON))
}, },
expected: JSONData{ expected: JSONData{
ID: "grafana-pyroscope-datasource", ID: "grafana-pyroscope-datasource",
Alias: "phlare", // Hardcoded from the parser AliasIDs: []string{"phlare"}, // Hardcoded from the parser
Type: TypeDataSource, Type: TypeDataSource,
Dependencies: Dependencies{ Dependencies: Dependencies{
GrafanaDependency: "", GrafanaDependency: "",
GrafanaVersion: "*", GrafanaVersion: "*",
@ -122,6 +123,24 @@ func Test_ReadPluginJSON(t *testing.T) {
}, },
}, },
}, },
{
name: "do not allow alias except for our hardcoded set",
pluginJSON: func(t *testing.T) io.ReadCloser {
pJSON := `{
"id": "my-custom-app",
"type": "app",
"aliasIDs": ["phlare"]
}`
return io.NopCloser(strings.NewReader(pJSON))
},
err: ErrUnsupportedAlias,
expected: JSONData{
ID: "my-custom-app",
AliasIDs: []string{"phlare"}, // Hardcoded from the parser
Type: "app",
Dependencies: Dependencies{},
},
},
} }
for _, tt := range tests { for _, tt := range tests {

View File

@ -32,9 +32,6 @@ type Plugin struct {
AngularDetected bool AngularDetected bool
// This will be moved to plugin.json when we have general support in gcom
Alias string
ExternalService *auth.ExternalService ExternalService *auth.ExternalService
} }
@ -76,7 +73,6 @@ func ToGrafanaDTO(p *plugins.Plugin) Plugin {
Module: p.Module, Module: p.Module,
BaseURL: p.BaseURL, BaseURL: p.BaseURL,
AngularDetected: p.AngularDetected, AngularDetected: p.AngularDetected,
Alias: p.Alias,
ExternalService: p.ExternalService, ExternalService: p.ExternalService,
} }
} }

View File

@ -33,7 +33,16 @@ export function hasPanelPlugin(id: string): boolean {
} }
export function getPanelPluginMeta(id: string): PanelPluginMeta { export function getPanelPluginMeta(id: string): PanelPluginMeta {
return config.panels[id] || Object.values(config.panels).find((p) => p.alias === id); const v = config.panels[id];
if (!v) {
// Check alias values before failing
for (const p of Object.values(config.panels)) {
if (p.aliasIDs?.includes(id)) {
return p;
}
}
}
return v;
} }
export function importPanelPluginFromMeta(meta: PanelPluginMeta): Promise<PanelPlugin> { export function importPanelPluginFromMeta(meta: PanelPluginMeta): Promise<PanelPlugin> {

View File

@ -2,6 +2,7 @@
"type": "datasource", "type": "datasource",
"name": "Grafana Pyroscope", "name": "Grafana Pyroscope",
"id": "grafana-pyroscope-datasource", "id": "grafana-pyroscope-datasource",
"aliasIDs": ["phlare"],
"category": "profiling", "category": "profiling",
"metrics": true, "metrics": true,

View File

@ -2,6 +2,7 @@
"type": "datasource", "type": "datasource",
"name": "TestData", "name": "TestData",
"id": "grafana-testdata-datasource", "id": "grafana-testdata-datasource",
"aliasIDs": ["testdata"],
"metrics": true, "metrics": true,
"logs": true, "logs": true,

View File

@ -2,6 +2,7 @@
"type": "panel", "type": "panel",
"name": "Annotations list", "name": "Annotations list",
"id": "annolist", "id": "annolist",
"aliasIDs": ["ryantxu-annolist-panel"],
"skipDataQuery": true, "skipDataQuery": true,

View File

@ -2,6 +2,7 @@
"type": "panel", "type": "panel",
"name": "Debug", "name": "Debug",
"id": "debug", "id": "debug",
"aliasIDs": ["debugX"],
"state": "alpha", "state": "alpha",
"info": { "info": {