diff --git a/packages/grafana-data/src/types/plugin.ts b/packages/grafana-data/src/types/plugin.ts index 42f18275064..6ca498a827e 100644 --- a/packages/grafana-data/src/types/plugin.ts +++ b/packages/grafana-data/src/types/plugin.ts @@ -58,7 +58,7 @@ export interface PluginMeta { info: PluginMetaInfo; includes?: PluginInclude[]; state?: PluginState; - alias?: string; + aliasIDs?: string[]; // System.load & relative URLS module: string; diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index 974d5a11e0a..b3088fb127a 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -19,6 +19,7 @@ import ( "github.com/grafana/grafana/pkg/setting" "github.com/grafana/grafana/pkg/tsdb/grafanads" "github.com/grafana/grafana/pkg/util" + "golang.org/x/exp/slices" ) 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{ ID: panel.ID, Name: panel.Name, - Alias: panel.Alias, + AliasIDs: panel.AliasIDs, Info: panel.Info, Module: panel.Module, BaseURL: panel.BaseURL, @@ -500,7 +501,7 @@ func (ap AvailablePlugins) Get(pluginType plugins.Type, pluginID string) (*avail return p, true } 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 } } diff --git a/pkg/plugins/manager/pipeline/bootstrap/steps.go b/pkg/plugins/manager/pipeline/bootstrap/steps.go index 65177dc6056..f2d61e58e91 100644 --- a/pkg/plugins/manager/pipeline/bootstrap/steps.go +++ b/pkg/plugins/manager/pipeline/bootstrap/steps.go @@ -29,7 +29,6 @@ func DefaultConstructFunc(signatureCalculator plugins.SignatureCalculator, asset // DefaultDecorateFuncs are the default DecorateFuncs used for the Decorate step of the Bootstrap stage. func DefaultDecorateFuncs(cfg *config.Cfg) []DecorateFunc { return []DecorateFunc{ - AliasDecorateFunc, AppDefaultNavURLDecorateFunc, AppChildDecorateFunc(cfg), } @@ -79,19 +78,6 @@ func (c *DefaultConstructor) Construct(ctx context.Context, src plugins.PluginSo 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. func AppDefaultNavURLDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) { if p.IsApp() { diff --git a/pkg/plugins/manager/registry/in_memory.go b/pkg/plugins/manager/registry/in_memory.go index 8a34f15ad29..8b533b985d8 100644 --- a/pkg/plugins/manager/registry/in_memory.go +++ b/pkg/plugins/manager/registry/in_memory.go @@ -48,8 +48,8 @@ func (i *InMemory) Add(_ context.Context, p *plugins.Plugin) error { i.mu.Lock() i.store[p.ID] = p - if p.Alias != "" { - i.alias[p.Alias] = p + for _, a := range p.AliasIDs { + i.alias[a] = p } i.mu.Unlock() @@ -64,8 +64,10 @@ func (i *InMemory) Remove(_ context.Context, pluginID string) error { i.mu.Lock() delete(i.store, pluginID) - if p != nil && p.Alias != "" { - delete(i.alias, p.Alias) + if p != nil { + for _, a := range p.AliasIDs { + delete(i.alias, a) + } } i.mu.Unlock() diff --git a/pkg/plugins/manager/registry/in_memory_test.go b/pkg/plugins/manager/registry/in_memory_test.go index ea5cbbfc706..25d3f42342a 100644 --- a/pkg/plugins/manager/registry/in_memory_test.go +++ b/pkg/plugins/manager/registry/in_memory_test.go @@ -278,6 +278,7 @@ func TestAliasSupport(t *testing.T) { pluginIdNew := "plugin-new" pluginIdOld := "plugin-old" + pluginIdOld2 := "plugin-old2" p, exists := i.Plugin(ctx, pluginIdNew) require.False(t, exists) @@ -285,9 +286,9 @@ func TestAliasSupport(t *testing.T) { pluginNew := &plugins.Plugin{ JSONData: plugins.JSONData{ - ID: pluginIdNew, + ID: pluginIdNew, + AliasIDs: []string{pluginIdOld, pluginIdOld2}, }, - Alias: pluginIdOld, // TODO: move to JSONData } err := i.Add(ctx, pluginNew) require.NoError(t, err) @@ -302,6 +303,11 @@ func TestAliasSupport(t *testing.T) { require.True(t, exists) 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 pluginOld := &plugins.Plugin{JSONData: plugins.JSONData{ ID: pluginIdOld, diff --git a/pkg/plugins/models.go b/pkg/plugins/models.go index 6e5a20344ac..8ccf777af72 100644 --- a/pkg/plugins/models.go +++ b/pkg/plugins/models.go @@ -243,18 +243,18 @@ type DataSourceDTO struct { } type PanelDTO struct { - ID string `json:"id"` - Name string `json:"name"` - Alias string `json:"alias,omitempty"` - Info Info `json:"info"` - HideFromList bool `json:"hideFromList"` - Sort int `json:"sort"` - SkipDataQuery bool `json:"skipDataQuery"` - ReleaseState string `json:"state"` - BaseURL string `json:"baseUrl"` - Signature string `json:"signature"` - Module string `json:"module"` - AngularDetected bool `json:"angularDetected"` + ID string `json:"id"` + Name string `json:"name"` + AliasIDs []string `json:"aliasIds,omitempty"` + Info Info `json:"info"` + HideFromList bool `json:"hideFromList"` + Sort int `json:"sort"` + SkipDataQuery bool `json:"skipDataQuery"` + ReleaseState string `json:"state"` + BaseURL string `json:"baseUrl"` + Signature string `json:"signature"` + Module string `json:"module"` + AngularDetected bool `json:"angularDetected"` } type AppDTO struct { diff --git a/pkg/plugins/plugindef/plugindef.cue b/pkg/plugins/plugindef/plugindef.cue index 61713483ce1..cc5685514ad 100644 --- a/pkg/plugins/plugindef/plugindef.cue +++ b/pkg/plugins/plugindef/plugindef.cue @@ -18,6 +18,10 @@ schemas: [{ 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)$" + // 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 // the UI. name: string diff --git a/pkg/plugins/plugindef/plugindef_types_gen.go b/pkg/plugins/plugindef/plugindef_types_gen.go index 29822d0c92b..d306736a37b 100644 --- a/pkg/plugins/plugindef/plugindef_types_gen.go +++ b/pkg/plugins/plugindef/plugindef_types_gen.go @@ -278,6 +278,10 @@ type PluginDef struct { // For data source plugins, if the plugin supports alerting. Requires `backend` to be set to `true`. 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 // queries. Annotations *bool `json:"annotations,omitempty"` diff --git a/pkg/plugins/plugins.go b/pkg/plugins/plugins.go index a4dba460e78..d8c95a8799d 100644 --- a/pkg/plugins/plugins.go +++ b/pkg/plugins/plugins.go @@ -29,6 +29,7 @@ var ( ErrPluginFileRead = errors.New("file could not be read") ErrUninstallInvalidPluginDir = errors.New("cannot recognize as plugin folder") 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 { @@ -64,9 +65,6 @@ type Plugin struct { log log.Logger 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 @@ -75,7 +73,7 @@ type JSONData struct { ID string `json:"id"` Type Type `json:"type"` Name string `json:"name"` - Alias string `json:"alias,omitempty"` + AliasIDs []string `json:"aliasIDs,omitempty"` Info Info `json:"info"` Dependencies Dependencies `json:"dependencies"` Includes []*Includes `json:"includes"` @@ -130,12 +128,20 @@ func ReadPluginJSON(reader io.Reader) (JSONData, error) { switch plugin.ID { case "grafana-piechart-panel": plugin.Name = "Pie Chart (old)" - case "grafana-pyroscope-datasource": // rebranding - plugin.Alias = "phlare" + case "grafana-pyroscope-datasource": + fallthrough case "grafana-testdata-datasource": - plugin.Alias = "testdata" - case "debug": // panel plugin used for testing - plugin.Alias = "debugX" + fallthrough + case "annolist": + 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 { diff --git a/pkg/plugins/plugins_test.go b/pkg/plugins/plugins_test.go index a701a0fded3..96eef9f0b59 100644 --- a/pkg/plugins/plugins_test.go +++ b/pkg/plugins/plugins_test.go @@ -107,14 +107,15 @@ func Test_ReadPluginJSON(t *testing.T) { pluginJSON: func(t *testing.T) io.ReadCloser { pJSON := `{ "id": "grafana-pyroscope-datasource", - "type": "datasource" + "type": "datasource", + "aliasIDs": ["phlare"] }` return io.NopCloser(strings.NewReader(pJSON)) }, expected: JSONData{ - ID: "grafana-pyroscope-datasource", - Alias: "phlare", // Hardcoded from the parser - Type: TypeDataSource, + ID: "grafana-pyroscope-datasource", + AliasIDs: []string{"phlare"}, // Hardcoded from the parser + Type: TypeDataSource, Dependencies: Dependencies{ GrafanaDependency: "", 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 { diff --git a/pkg/services/pluginsintegration/pluginstore/plugins.go b/pkg/services/pluginsintegration/pluginstore/plugins.go index 2ac1fad9ae9..7ead33b9ab6 100644 --- a/pkg/services/pluginsintegration/pluginstore/plugins.go +++ b/pkg/services/pluginsintegration/pluginstore/plugins.go @@ -32,9 +32,6 @@ type Plugin struct { AngularDetected bool - // This will be moved to plugin.json when we have general support in gcom - Alias string - ExternalService *auth.ExternalService } @@ -76,7 +73,6 @@ func ToGrafanaDTO(p *plugins.Plugin) Plugin { Module: p.Module, BaseURL: p.BaseURL, AngularDetected: p.AngularDetected, - Alias: p.Alias, ExternalService: p.ExternalService, } } diff --git a/public/app/features/plugins/importPanelPlugin.ts b/public/app/features/plugins/importPanelPlugin.ts index deb130f838a..87e215e09f9 100644 --- a/public/app/features/plugins/importPanelPlugin.ts +++ b/public/app/features/plugins/importPanelPlugin.ts @@ -33,7 +33,16 @@ export function hasPanelPlugin(id: string): boolean { } 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 { diff --git a/public/app/plugins/datasource/grafana-pyroscope-datasource/plugin.json b/public/app/plugins/datasource/grafana-pyroscope-datasource/plugin.json index 890e79e5e3b..ba90841a3e8 100644 --- a/public/app/plugins/datasource/grafana-pyroscope-datasource/plugin.json +++ b/public/app/plugins/datasource/grafana-pyroscope-datasource/plugin.json @@ -2,6 +2,7 @@ "type": "datasource", "name": "Grafana Pyroscope", "id": "grafana-pyroscope-datasource", + "aliasIDs": ["phlare"], "category": "profiling", "metrics": true, diff --git a/public/app/plugins/datasource/grafana-testdata-datasource/plugin.json b/public/app/plugins/datasource/grafana-testdata-datasource/plugin.json index 373ad10e303..2792259a779 100644 --- a/public/app/plugins/datasource/grafana-testdata-datasource/plugin.json +++ b/public/app/plugins/datasource/grafana-testdata-datasource/plugin.json @@ -2,6 +2,7 @@ "type": "datasource", "name": "TestData", "id": "grafana-testdata-datasource", + "aliasIDs": ["testdata"], "metrics": true, "logs": true, diff --git a/public/app/plugins/panel/annolist/plugin.json b/public/app/plugins/panel/annolist/plugin.json index 269b9bec6ab..37765ec65ff 100644 --- a/public/app/plugins/panel/annolist/plugin.json +++ b/public/app/plugins/panel/annolist/plugin.json @@ -2,6 +2,7 @@ "type": "panel", "name": "Annotations list", "id": "annolist", + "aliasIDs": ["ryantxu-annolist-panel"], "skipDataQuery": true, diff --git a/public/app/plugins/panel/debug/plugin.json b/public/app/plugins/panel/debug/plugin.json index 12ed125426c..d393eaf296b 100644 --- a/public/app/plugins/panel/debug/plugin.json +++ b/public/app/plugins/panel/debug/plugin.json @@ -2,6 +2,7 @@ "type": "panel", "name": "Debug", "id": "debug", + "aliasIDs": ["debugX"], "state": "alpha", "info": {