Plugins: Bump SystemJS to 6.14.2 (#70068)

* chore(plugins): bump systemjs to latest version

* refactor(plugins): switch runtime over to use latest systemjs, add typings

* refactor(plugins): use latest systemjs APIs for runtime dependency resolution

* refactor(plugins): return valid urls from backend for latest systemjs import to work

* revert(plugins): remove cfg from assetpath in favour of relative paths

* fix(plugins): useDefault for systemjs deps to solve undefined errors

* feat(plugins): add basic support for loading plugins via CDN

* fix(plugins): load nested plugins with latest systemjs

* feat(plugins): add back ability to transform plugin src for cdns

* feat(plugins): get caching for module.js working, clean up

* refactor(plugin_loader): create buildImportMap fn and more clean up

* refactor(angularapp): use buildImportMap for dependencies and clean up

* test(plugin_loader): fix failing test due to systemjs update

* test(jest): mock systemjs amd extra in tests to prevent it breaking tests

* chore(plugins): remove systemjs module-types extra, already included with system

* fix(plugins): update key for invalidating cached plugins

* fix: move systemjs amd define to another global so monaco can load

* refactor(plugins): clean up cache buster and tests

* chore(plugins): remove debug

* refactor(plugins): move systemjs define to keep global cleaner

* fix(plugins): set useDefault so system modules lodash references dont fail

* feat(plugins): hook systemjs onload so stylesheets are applied to the dom

* refactor(plugins): wrap amd formatted plugins in iife to prevent define collision

* feat(plugins): support system module format for legacy plugins

* test(plugincachebuster): update tests to match latest implementation

* test(plugins-loader): fix up tests post module property change

* fix test

* Update pkg/plugins/manager/loader/assetpath/assetpath.go

Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>

* chore(plugin_loader): remove stray import from merge conflict

* Revert "Update pkg/plugins/manager/loader/assetpath/assetpath.go"

This reverts commit 0df57d1cf20f49c22c93369001c70aae46a97c42.

* fix(plugin_loader): set use default for shared plugin dependencies

* refactor(plugins): use leading slash for Module and BaseUrl

* fix(plugins): fix resolve appending extension to cache query param

* refactor(plugins): align baseurl and module paths

* refactor(plugins): update builtInPlugins keys to match naming convention

* refactor(plugins): minor loader clean up, fix up types

* test(plugins): fix failing tests

* refactor(plugins): rename cache buster systemjs plugin to cache

* refactor(plugins): separate plugin_loader into smaller files

* chore(plugins): clean up plugin_loader types

* chore(plugin_sandbox): fix typescript error

* chore(npm): remove unplug debug and pin systemjs to 6.14.1

* refactor(plugins-cdn): update loaders to use absolute module url from backend

* fix(plugins): escape period in systemjs  module regex

* chore(plugins): delete redundant systemjs plugins

* refactor(plugin_loader): move hooks into own file, add types

* test(plugins): add tests for systemjs loader hooks

* chore(plugins): rename systemjshooks file

* chore(plugins): remove redundant systemjs cdn backend code

* fix(plugins): handle loading with config.appSubUrl

* chore(plugins): delete redundant plugin-cdn angular code

* test(plugins): fix failing systemjs test missing pluginsCDNBaseUrl

* refactor(plugins): backend provides base and module properties with AppSubUrl

* fix(plugins): consider AppSubUrl for plugin logos

* fix(plugins): use isHostedOnCDN util when checking for cdn hosted plugins

* add new appSubURL field to config

* refactor relative URL func

* fix path for core app

* refactor asset path input

* fix(plugins): catch errors in loadPluginCss

* feat(plugins-cdn): selectively transform sourceMapURL

* re-add deleted test case

* chore(plugins): bump to latest systemjs@6.14.2

* feat(plugins): add systemjs-cjs-extra for loading commonjs plugins

---------

Co-authored-by: Will Browne <will.browne@grafana.com>
Co-authored-by: Will Browne <wbrowne@users.noreply.github.com>
This commit is contained in:
Jack Westbrook
2023-08-31 15:45:44 +02:00
committed by GitHub
co-authored by Will Browne Will Browne
parent b2f7476bb4
commit 62821c69b3
49 changed files with 1155 additions and 887 deletions
+4 -2
View File
@@ -39,7 +39,8 @@ type Cfg struct {
GrafanaComURL string
GrafanaAppURL string
GrafanaAppURL string
GrafanaAppSubURL string
Features plugins.FeatureToggles
@@ -48,7 +49,7 @@ type Cfg struct {
func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSettings, pluginsAllowUnsigned []string,
awsAllowedAuthProviders []string, awsAssumeRoleEnabled bool, awsExternalId string, azure *azsettings.AzureSettings, secureSocksDSProxy setting.SecureSocksDSProxySettings,
grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, appURL string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool,
grafanaVersion string, logDatasourceRequests bool, pluginsCDNURLTemplate string, appURL string, appSubURL string, tracing Tracing, features plugins.FeatureToggles, angularSupportEnabled bool,
grafanaComURL string) *Cfg {
return &Cfg{
log: log.New("plugin.cfg"),
@@ -67,6 +68,7 @@ func NewCfg(devMode bool, pluginsPath string, pluginSettings setting.PluginSetti
Tracing: tracing,
GrafanaComURL: grafanaComURL,
GrafanaAppURL: appURL,
GrafanaAppSubURL: appSubURL,
Features: features,
AngularSupportEnabled: angularSupportEnabled,
}
@@ -17,47 +17,57 @@ import (
// on the plugins CDN, and it will switch to the correct implementation depending on the plugin and the config.
type Service struct {
cdn *pluginscdn.Service
cfg *config.Cfg
}
func ProvideService(cdn *pluginscdn.Service) *Service {
return &Service{cdn: cdn}
func ProvideService(cfg *config.Cfg, cdn *pluginscdn.Service) *Service {
return &Service{cfg: cfg, cdn: cdn}
}
type PluginInfo struct {
pluginJSON plugins.JSONData
class plugins.Class
dir string
}
func NewPluginInfo(pluginJSON plugins.JSONData, class plugins.Class, fs plugins.FS) PluginInfo {
return PluginInfo{
pluginJSON: pluginJSON,
class: class,
dir: fs.Base(),
}
}
func DefaultService(cfg *config.Cfg) *Service {
return &Service{cdn: pluginscdn.ProvideService(cfg)}
return &Service{cfg: cfg, cdn: pluginscdn.ProvideService(cfg)}
}
// Base returns the base path for the specified plugin.
func (s *Service) Base(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string) (string, error) {
if class == plugins.ClassCore {
return path.Join("public/app/plugins", string(pluginJSON.Type), filepath.Base(pluginDir)), nil
func (s *Service) Base(n PluginInfo) (string, error) {
if n.class == plugins.ClassCore {
return path.Join("/", s.cfg.GrafanaAppSubURL, "/public/app/plugins", string(n.pluginJSON.Type), filepath.Base(n.dir)), nil
}
if s.cdn.PluginSupported(pluginJSON.ID) {
return s.cdn.SystemJSAssetPath(pluginJSON.ID, pluginJSON.Info.Version, "")
if s.cdn.PluginSupported(n.pluginJSON.ID) {
return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "")
}
return path.Join("public/plugins", pluginJSON.ID), nil
return path.Join("/", s.cfg.GrafanaAppSubURL, "/public/plugins", n.pluginJSON.ID), nil
}
// Module returns the module.js path for the specified plugin.
func (s *Service) Module(pluginJSON plugins.JSONData, class plugins.Class, pluginDir string) (string, error) {
if class == plugins.ClassCore {
return path.Join("app/plugins", string(pluginJSON.Type), filepath.Base(pluginDir), "module"), nil
func (s *Service) Module(n PluginInfo) (string, error) {
if n.class == plugins.ClassCore {
return path.Join("core:plugin", filepath.Base(n.dir)), nil
}
if s.cdn.PluginSupported(pluginJSON.ID) {
return s.cdn.SystemJSAssetPath(pluginJSON.ID, pluginJSON.Info.Version, "module")
if s.cdn.PluginSupported(n.pluginJSON.ID) {
return s.cdn.AssetURL(n.pluginJSON.ID, n.pluginJSON.Info.Version, "module.js")
}
return path.Join("plugins", pluginJSON.ID, "module"), nil
return path.Join("/", s.cfg.GrafanaAppSubURL, "/public/plugins", n.pluginJSON.ID, "module.js"), nil
}
// RelativeURL returns the relative URL for an arbitrary plugin asset.
// If pathStr is an empty string, defaultStr is returned.
func (s *Service) RelativeURL(p *plugins.Plugin, pathStr, defaultStr string) (string, error) {
if pathStr == "" {
return defaultStr, nil
}
if s.cdn.PluginSupported(p.ID) {
// CDN
return s.cdn.NewCDNURLConstructor(p.ID, p.Info.Version).StringPath(pathStr)
func (s *Service) RelativeURL(n PluginInfo, pathStr string) (string, error) {
if s.cdn.PluginSupported(n.pluginJSON.ID) {
return s.cdn.NewCDNURLConstructor(n.pluginJSON.ID, n.pluginJSON.Info.Version).StringPath(pathStr)
}
// Local
u, err := url.Parse(pathStr)
@@ -67,9 +77,20 @@ func (s *Service) RelativeURL(p *plugins.Plugin, pathStr, defaultStr string) (st
if u.IsAbs() {
return pathStr, nil
}
// is set as default or has already been prefixed with base path
if pathStr == defaultStr || strings.HasPrefix(pathStr, p.BaseURL) {
baseURL, err := s.Base(n)
if err != nil {
return "", err
}
// has already been prefixed with base path
if strings.HasPrefix(pathStr, baseURL) {
return pathStr, nil
}
return path.Join(p.BaseURL, pathStr), nil
return path.Join(baseURL, pathStr), nil
}
// DefaultLogoPath returns the default logo path for the specified plugin type.
func (s *Service) DefaultLogoPath(pluginType plugins.Type) string {
return path.Join("/", s.cfg.GrafanaAppSubURL, fmt.Sprintf("/public/img/icn-%s.svg", string(pluginType)))
}
@@ -1,17 +1,20 @@
package assetpath
import (
"net/url"
"strings"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
"github.com/stretchr/testify/require"
)
func extPath(pluginID string) string {
return "/grafana/data/plugins/" + pluginID
func extPath(pluginID string) *fakes.FakePluginFiles {
return fakes.NewFakePluginFiles(pluginID)
}
func TestService(t *testing.T) {
@@ -33,15 +36,16 @@ func TestService(t *testing.T) {
},
} {
t.Run(tc.name, func(t *testing.T) {
svc := ProvideService(pluginscdn.ProvideService(&config.Cfg{
cfg := &config.Cfg{
PluginsCDNURLTemplate: tc.cdnBaseURL,
PluginSettings: map[string]map[string]string{
"one": {"cdn": "true"},
"two": {},
},
}))
}
svc := ProvideService(cfg, pluginscdn.ProvideService(cfg))
const tableOldPath = "/grafana/public/app/plugins/panel/table-old"
tableOldFS := fakes.NewFakePluginFiles("/grafana/public/app/plugins/panel/table-old")
jsonData := map[string]plugins.JSONData{
"table-old": {ID: "table-old", Info: plugins.Info{Version: "1.0.0"}},
@@ -56,60 +60,110 @@ func TestService(t *testing.T) {
})
t.Run("Base", func(t *testing.T) {
base, err := svc.Base(jsonData["one"], plugins.ClassExternal, extPath("one"))
base, err := svc.Base(NewPluginInfo(jsonData["one"], plugins.ClassExternal, extPath("one")))
require.NoError(t, err)
require.Equal(t, "plugin-cdn/one/1.0.0/public/plugins/one", base)
base, err = svc.Base(jsonData["two"], plugins.ClassExternal, extPath("two"))
u, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one")
require.NoError(t, err)
require.Equal(t, "public/plugins/two", base)
require.Equal(t, u, base)
base, err = svc.Base(jsonData["table-old"], plugins.ClassCore, tableOldPath)
base, err = svc.Base(NewPluginInfo(jsonData["two"], plugins.ClassExternal, extPath("two")))
require.NoError(t, err)
require.Equal(t, "public/app/plugins/table-old", base)
require.Equal(t, "/public/plugins/two", base)
base, err = svc.Base(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS))
require.NoError(t, err)
require.Equal(t, "/public/app/plugins/table-old", base)
})
t.Run("Module", func(t *testing.T) {
module, err := svc.Module(jsonData["one"], plugins.ClassExternal, extPath("one"))
module, err := svc.Module(NewPluginInfo(jsonData["one"], plugins.ClassExternal, extPath("one")))
require.NoError(t, err)
require.Equal(t, "plugin-cdn/one/1.0.0/public/plugins/one/module", module)
module, err = svc.Module(jsonData["two"], plugins.ClassExternal, extPath("two"))
u, err := url.JoinPath(tc.cdnBaseURL, "/one/1.0.0/public/plugins/one/module.js")
require.NoError(t, err)
require.Equal(t, "plugins/two/module", module)
require.Equal(t, u, module)
module, err = svc.Module(jsonData["table-old"], plugins.ClassCore, tableOldPath)
module, err = svc.Module(NewPluginInfo(jsonData["two"], plugins.ClassExternal, extPath("two")))
require.NoError(t, err)
require.Equal(t, "app/plugins/table-old/module", module)
require.Equal(t, "/public/plugins/two/module.js", module)
module, err = svc.Module(NewPluginInfo(jsonData["table-old"], plugins.ClassCore, tableOldFS))
require.NoError(t, err)
require.Equal(t, "core:plugin/table-old", module)
})
t.Run("RelativeURL", func(t *testing.T) {
pluginsMap := map[string]*plugins.Plugin{
"one": {
JSONData: plugins.JSONData{ID: "one", Info: plugins.Info{Version: "1.0.0"}},
BaseURL: "plugin-cdn/one/1.0.0/public/pluginsMap/one",
},
"two": {
JSONData: plugins.JSONData{ID: "two", Info: plugins.Info{Version: "2.0.0"}},
BaseURL: "public/pluginsMap/two",
},
}
u, err := svc.RelativeURL(pluginsMap["one"], "", "default")
require.NoError(t, err)
require.Equal(t, "default", u)
u, err = svc.RelativeURL(pluginsMap["one"], "path/to/file.txt", "default")
u, err := svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one")), "")
require.NoError(t, err)
// given an empty path, base URL will be returned
baseURL, err := svc.Base(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one")))
require.NoError(t, err)
require.Equal(t, baseURL, u)
u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["one"].JSONData, plugins.ClassExternal, extPath("one")), "path/to/file.txt")
require.NoError(t, err)
require.Equal(t, strings.TrimRight(tc.cdnBaseURL, "/")+"/one/1.0.0/public/plugins/one/path/to/file.txt", u)
u, err = svc.RelativeURL(pluginsMap["two"], "path/to/file.txt", "default")
u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, extPath("two")), "path/to/file.txt")
require.NoError(t, err)
require.Equal(t, "public/pluginsMap/two/path/to/file.txt", u)
require.Equal(t, "/public/plugins/two/path/to/file.txt", u)
u, err = svc.RelativeURL(pluginsMap["two"], "default", "default")
u, err = svc.RelativeURL(NewPluginInfo(pluginsMap["two"].JSONData, plugins.ClassExternal, extPath("two")), "default")
require.NoError(t, err)
require.Equal(t, "default", u)
require.Equal(t, "/public/plugins/two/default", u)
})
})
}
t.Run("With App Sub URL", func(t *testing.T) {
for _, tc := range []struct {
appSubURL string
}{
{
appSubURL: "grafana",
},
{
appSubURL: "/grafana",
},
{
appSubURL: "grafana/",
},
{
appSubURL: "/grafana/",
},
} {
cfg := &config.Cfg{GrafanaAppSubURL: tc.appSubURL}
svc := ProvideService(cfg, pluginscdn.ProvideService(cfg))
dir := "/plugins/test-datasource"
p := plugins.JSONData{ID: "test-datasource"}
fs := fakes.NewFakePluginFiles(dir)
base, err := svc.Base(NewPluginInfo(p, plugins.ClassExternal, fs))
require.NoError(t, err)
require.Equal(t, "/grafana/public/plugins/test-datasource", base)
mod, err := svc.Module(NewPluginInfo(p, plugins.ClassExternal, fs))
require.NoError(t, err)
require.Equal(t, "/grafana/public/plugins/test-datasource/module.js", mod)
base, err = svc.Base(NewPluginInfo(p, plugins.ClassCore, fs))
require.NoError(t, err)
require.Equal(t, "/grafana/public/app/plugins/test-datasource", base)
mod, err = svc.Module(NewPluginInfo(p, plugins.ClassCore, fs))
require.NoError(t, err)
require.Equal(t, "core:plugin/test-datasource", mod)
}
})
}
+66 -26
View File
@@ -82,8 +82,8 @@ func TestLoader_Load(t *testing.T) {
},
Description: "Data source for Amazon AWS monitoring service",
Logos: plugins.Logos{
Small: "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
Large: "public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
Small: "/public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
Large: "/public/app/plugins/datasource/cloudwatch/img/amazon-web-services.png",
},
},
Includes: []*plugins.Includes{
@@ -105,8 +105,8 @@ func TestLoader_Load(t *testing.T) {
Backend: true,
QueryOptions: map[string]bool{"minInterval": true},
},
Module: "app/plugins/datasource/cloudwatch/module",
BaseURL: "public/app/plugins/datasource/cloudwatch",
Module: "core:plugin/cloudwatch",
BaseURL: "/public/app/plugins/datasource/cloudwatch",
FS: mustNewStaticFSForTests(t, filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch")),
Signature: plugins.SignatureStatusInternal,
@@ -132,8 +132,8 @@ func TestLoader_Load(t *testing.T) {
},
Version: "1.0.0",
Logos: plugins.Logos{
Small: "public/img/icn-datasource.svg",
Large: "public/img/icn-datasource.svg",
Small: "/public/img/icn-datasource.svg",
Large: "/public/img/icn-datasource.svg",
},
Description: "Test",
},
@@ -145,8 +145,8 @@ func TestLoader_Load(t *testing.T) {
Backend: true,
State: "alpha",
},
Module: "plugins/test-datasource/module",
BaseURL: "public/plugins/test-datasource",
Module: "/public/plugins/test-datasource/module.js",
BaseURL: "/public/plugins/test-datasource",
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/valid-v2-signature/plugin/")),
Signature: "valid",
SignatureType: plugins.SignatureTypeGrafana,
@@ -171,8 +171,8 @@ func TestLoader_Load(t *testing.T) {
URL: "http://test.com",
},
Logos: plugins.Logos{
Small: "public/plugins/test-app/img/logo_small.png",
Large: "public/plugins/test-app/img/logo_large.png",
Small: "/public/plugins/test-app/img/logo_small.png",
Large: "/public/plugins/test-app/img/logo_large.png",
},
Links: []plugins.InfoLink{
{Name: "Project site", URL: "http://project.com"},
@@ -180,8 +180,8 @@ func TestLoader_Load(t *testing.T) {
},
Description: "Official Grafana Test App & Dashboard bundle",
Screenshots: []plugins.Screenshots{
{Path: "public/plugins/test-app/img/screenshot1.png", Name: "img1"},
{Path: "public/plugins/test-app/img/screenshot2.png", Name: "img2"},
{Path: "/public/plugins/test-app/img/screenshot1.png", Name: "img1"},
{Path: "/public/plugins/test-app/img/screenshot2.png", Name: "img2"},
},
Version: "1.0.0",
Updated: "2015-02-10",
@@ -222,8 +222,8 @@ func TestLoader_Load(t *testing.T) {
},
},
Class: plugins.ClassExternal,
Module: "plugins/test-app/module",
BaseURL: "public/plugins/test-app",
Module: "/public/plugins/test-app/module.js",
BaseURL: "/public/plugins/test-app",
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/includes-symlinks")),
Signature: "valid",
SignatureType: plugins.SignatureTypeGrafana,
@@ -249,8 +249,8 @@ func TestLoader_Load(t *testing.T) {
URL: "https://grafana.com",
},
Logos: plugins.Logos{
Small: "public/img/icn-datasource.svg",
Large: "public/img/icn-datasource.svg",
Small: "/public/img/icn-datasource.svg",
Large: "/public/img/icn-datasource.svg",
},
Description: "Test",
},
@@ -262,8 +262,8 @@ func TestLoader_Load(t *testing.T) {
State: plugins.ReleaseStateAlpha,
},
Class: plugins.ClassExternal,
Module: "plugins/test-datasource/module",
BaseURL: "public/plugins/test-datasource",
Module: "/public/plugins/test-datasource/module.js",
BaseURL: "/public/plugins/test-datasource",
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/unsigned-datasource/plugin")),
Signature: "unsigned",
},
@@ -295,8 +295,8 @@ func TestLoader_Load(t *testing.T) {
URL: "https://grafana.com",
},
Logos: plugins.Logos{
Small: "public/img/icn-datasource.svg",
Large: "public/img/icn-datasource.svg",
Small: "/public/img/icn-datasource.svg",
Large: "/public/img/icn-datasource.svg",
},
Description: "Test",
},
@@ -308,8 +308,8 @@ func TestLoader_Load(t *testing.T) {
State: plugins.ReleaseStateAlpha,
},
Class: plugins.ClassExternal,
Module: "plugins/test-datasource/module",
BaseURL: "public/plugins/test-datasource",
Module: "/public/plugins/test-datasource/module.js",
BaseURL: "/public/plugins/test-datasource",
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/unsigned-datasource/plugin")),
Signature: plugins.SignatureStatusUnsigned,
},
@@ -374,8 +374,8 @@ func TestLoader_Load(t *testing.T) {
{Name: "License & Terms", URL: "http://license.com"},
},
Logos: plugins.Logos{
Small: "public/img/icn-app.svg",
Large: "public/img/icn-app.svg",
Small: "/public/img/icn-app.svg",
Large: "/public/img/icn-app.svg",
},
Updated: "2015-02-10",
},
@@ -394,8 +394,48 @@ func TestLoader_Load(t *testing.T) {
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/test-app-with-includes")),
Class: plugins.ClassExternal,
Signature: plugins.SignatureStatusUnsigned,
Module: "plugins/test-app/module",
BaseURL: "public/plugins/test-app",
Module: "/public/plugins/test-app/module.js",
BaseURL: "/public/plugins/test-app",
},
},
},
{
name: "Load a plugin with app sub url set",
class: plugins.ClassExternal,
cfg: &config.Cfg{
DevMode: true,
GrafanaAppSubURL: "grafana",
},
pluginPaths: []string{"../testdata/unsigned-datasource"},
want: []*plugins.Plugin{
{
JSONData: plugins.JSONData{
ID: "test-datasource",
Type: plugins.TypeDataSource,
Name: "Test",
Info: plugins.Info{
Author: plugins.InfoLink{
Name: "Grafana Labs",
URL: "https://grafana.com",
},
Logos: plugins.Logos{
Small: "/grafana/public/img/icn-datasource.svg",
Large: "/grafana/public/img/icn-datasource.svg",
},
Description: "Test",
},
Dependencies: plugins.Dependencies{
GrafanaVersion: "*",
Plugins: []plugins.Dependency{},
},
Backend: true,
State: plugins.ReleaseStateAlpha,
},
Class: plugins.ClassExternal,
Module: "/grafana/public/plugins/test-datasource/module.js",
BaseURL: "/grafana/public/plugins/test-datasource",
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/unsigned-datasource/plugin")),
Signature: plugins.SignatureStatusUnsigned,
},
},
},
@@ -48,7 +48,7 @@ func New(cfg *config.Cfg, opts Opts) *Bootstrap {
}
if opts.DecorateFuncs == nil {
opts.DecorateFuncs = DefaultDecorateFuncs
opts.DecorateFuncs = DefaultDecorateFuncs(cfg)
}
return &Bootstrap{
@@ -25,27 +25,27 @@ func NewDefaultPluginFactory(assetPath *assetpath.Service) *DefaultPluginFactory
func (f *DefaultPluginFactory) createPlugin(p plugins.FoundPlugin, class plugins.Class,
sig plugins.Signature) (*plugins.Plugin, error) {
baseURL, err := f.assetPath.Base(p.JSONData, class, p.FS.Base())
info := assetpath.NewPluginInfo(p.JSONData, class, p.FS)
baseURL, err := f.assetPath.Base(info)
if err != nil {
return nil, fmt.Errorf("base url: %w", err)
}
moduleURL, err := f.assetPath.Module(p.JSONData, class, p.FS.Base())
moduleURL, err := f.assetPath.Module(info)
if err != nil {
return nil, fmt.Errorf("module url: %w", err)
}
plugin := &plugins.Plugin{
JSONData: p.JSONData,
Class: class,
FS: p.FS,
BaseURL: baseURL,
Module: moduleURL,
Class: class,
Signature: sig.Status,
SignatureType: sig.Type,
SignatureOrg: sig.SigningOrg,
}
plugin.SetLogger(log.New(fmt.Sprintf("plugin.%s", plugin.ID)))
plugin.SetLogger(log.New(fmt.Sprintf("plugin.%s", plugin.ID)))
if err = setImages(plugin, f.assetPath); err != nil {
return nil, err
}
@@ -54,23 +54,25 @@ func (f *DefaultPluginFactory) createPlugin(p plugins.FoundPlugin, class plugins
}
func setImages(p *plugins.Plugin, assetPath *assetpath.Service) error {
info := assetpath.NewPluginInfo(p.JSONData, p.Class, p.FS)
var err error
for _, dst := range []*string{&p.Info.Logos.Small, &p.Info.Logos.Large} {
*dst, err = assetPath.RelativeURL(p, *dst, defaultLogoPath(p.Type))
if len(*dst) == 0 {
*dst = assetPath.DefaultLogoPath(p.Type)
continue
}
*dst, err = assetPath.RelativeURL(info, *dst)
if err != nil {
return fmt.Errorf("logo: %w", err)
}
}
for i := 0; i < len(p.Info.Screenshots); i++ {
screenshot := &p.Info.Screenshots[i]
screenshot.Path, err = assetPath.RelativeURL(p, screenshot.Path, "")
screenshot.Path, err = assetPath.RelativeURL(info, screenshot.Path)
if err != nil {
return fmt.Errorf("screenshot %d relative url: %w", i, err)
}
}
return nil
}
func defaultLogoPath(pluginType plugins.Type) string {
return fmt.Sprintf("public/img/icn-%s.svg", string(pluginType))
}
+17 -13
View File
@@ -7,9 +7,9 @@ import (
"github.com/grafana/grafana/pkg/infra/slugify"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/log"
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
"github.com/grafana/grafana/pkg/util"
)
// DefaultConstructor implements the default ConstructFunc used for the Construct step of the Bootstrap stage.
@@ -27,10 +27,12 @@ func DefaultConstructFunc(signatureCalculator plugins.SignatureCalculator, asset
}
// DefaultDecorateFuncs are the default DecorateFuncs used for the Decorate step of the Bootstrap stage.
var DefaultDecorateFuncs = []DecorateFunc{
AliasDecorateFunc,
AppDefaultNavURLDecorateFunc,
AppChildDecorateFunc,
func DefaultDecorateFuncs(cfg *config.Cfg) []DecorateFunc {
return []DecorateFunc{
AliasDecorateFunc,
AppDefaultNavURLDecorateFunc,
AppChildDecorateFunc(cfg),
}
}
// NewDefaultConstructor returns a new DefaultConstructor.
@@ -123,24 +125,26 @@ func setDefaultNavURL(p *plugins.Plugin) {
}
// AppChildDecorateFunc is a DecorateFunc that configures child plugins of app plugins.
func AppChildDecorateFunc(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
if p.Parent != nil && p.Parent.IsApp() {
configureAppChildPlugin(p.Parent, p)
func AppChildDecorateFunc(cfg *config.Cfg) DecorateFunc {
return func(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
if p.Parent != nil && p.Parent.IsApp() {
configureAppChildPlugin(cfg, p.Parent, p)
}
return p, nil
}
return p, nil
}
func configureAppChildPlugin(parent *plugins.Plugin, child *plugins.Plugin) {
func configureAppChildPlugin(cfg *config.Cfg, parent *plugins.Plugin, child *plugins.Plugin) {
if !parent.IsApp() {
return
}
appSubPath := strings.ReplaceAll(strings.Replace(child.FS.Base(), parent.FS.Base(), "", 1), "\\", "/")
child.IncludedInAppID = parent.ID
child.BaseURL = parent.BaseURL
appSubPath := strings.ReplaceAll(strings.Replace(child.FS.Base(), parent.FS.Base(), "", 1), "\\", "/")
if parent.IsCorePlugin() {
child.Module = util.JoinURLFragments("app/plugins/app/"+parent.ID, appSubPath) + "/module"
child.Module = path.Join("core:plugin", parent.ID, appSubPath)
} else {
child.Module = util.JoinURLFragments("plugins/"+parent.ID, appSubPath) + "/module"
child.Module = path.Join("/", cfg.GrafanaAppSubURL, "/public/plugins", parent.ID, appSubPath, "module.js")
}
}
@@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/log"
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
)
@@ -65,7 +66,7 @@ func TestSetDefaultNavURL(t *testing.T) {
})
}
func TestSetPathsBasedOnApp(t *testing.T) {
func Test_configureAppChildPlugin(t *testing.T) {
t.Run("When setting paths based on core plugin on Windows", func(t *testing.T) {
child := &plugins.Plugin{
FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource"),
@@ -77,13 +78,42 @@ func TestSetPathsBasedOnApp(t *testing.T) {
},
Class: plugins.ClassCore,
FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app"),
BaseURL: "public/app/plugins/app/testdata-app",
BaseURL: "/public/app/plugins/app/testdata-app",
}
configureAppChildPlugin(parent, child)
configureAppChildPlugin(&config.Cfg{}, parent, child)
require.Equal(t, "app/plugins/app/testdata-app/datasources/datasource/module", child.Module)
require.Equal(t, "core:plugin/testdata-app/datasources/datasource", child.Module)
require.Equal(t, "testdata-app", child.IncludedInAppID)
require.Equal(t, "public/app/plugins/app/testdata-app", child.BaseURL)
require.Equal(t, "/public/app/plugins/app/testdata-app", child.BaseURL)
t.Run("App sub URL has no effect on Core plugins", func(t *testing.T) {
configureAppChildPlugin(&config.Cfg{GrafanaAppSubURL: "/grafana"}, parent, child)
require.Equal(t, "core:plugin/testdata-app/datasources/datasource", child.Module)
require.Equal(t, "testdata-app", child.IncludedInAppID)
require.Equal(t, "/public/app/plugins/app/testdata-app", child.BaseURL)
})
})
t.Run("When setting paths based on external plugin with app sub URL", func(t *testing.T) {
child := &plugins.Plugin{
FS: fakes.NewFakePluginFiles("/plugins/parent-app/child-panel"),
}
parent := &plugins.Plugin{
JSONData: plugins.JSONData{
Type: plugins.TypeApp,
ID: "testdata-app",
},
Class: plugins.ClassExternal,
FS: fakes.NewFakePluginFiles("/plugins/parent-app"),
BaseURL: "/grafana/plugins/parent-app",
}
configureAppChildPlugin(&config.Cfg{GrafanaAppSubURL: "/grafana"}, parent, child)
require.Equal(t, "/grafana/public/plugins/testdata-app/child-panel/module.js", child.Module)
require.Equal(t, "testdata-app", child.IncludedInAppID)
require.Equal(t, "/grafana/plugins/parent-app", child.BaseURL)
})
}
-18
View File
@@ -10,9 +10,6 @@ import (
const (
// cdnAssetPathTemplate is the relative path template used to locate plugin CDN assets
cdnAssetPathTemplate = "{id}/{version}/public/plugins/{id}/{assetPath}"
// systemJSCDNURLTemplate is a special path template used by system.js to identify plugin CDN assets
systemJSCDNURLTemplate = "plugin-cdn/" + cdnAssetPathTemplate
)
var ErrPluginNotCDN = errors.New("plugin is not a cdn plugin")
@@ -57,21 +54,6 @@ func (s *Service) BaseURL() (string, error) {
return s.cfg.PluginsCDNURLTemplate, nil
}
// SystemJSAssetPath returns a system-js path for the specified asset on the plugins CDN.
// The returned path will follow the template specified in systemJSCDNURLTemplate.
// If assetPath is an empty string, the base path for the plugin is returned.
func (s *Service) SystemJSAssetPath(pluginID, pluginVersion, assetPath string) (string, error) {
u, err := URLConstructor{
cdnURLTemplate: systemJSCDNURLTemplate,
pluginID: pluginID,
pluginVersion: pluginVersion,
}.Path(assetPath)
if err != nil {
return "", err
}
return u.String(), nil
}
// AssetURL returns the URL of a CDN asset for a CDN plugin. If the specified plugin is not a CDN plugin,
// it returns ErrPluginNotCDN.
func (s *Service) AssetURL(pluginID, pluginVersion, assetPath string) (string, error) {