2021-11-01 04:53:33 -05:00
|
|
|
package loader
|
|
|
|
|
|
|
|
import (
|
2022-01-14 06:30:39 -06:00
|
|
|
"context"
|
2023-04-12 04:34:16 -05:00
|
|
|
"fmt"
|
2021-11-01 04:53:33 -05:00
|
|
|
"path/filepath"
|
|
|
|
"sort"
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
2022-01-14 06:30:39 -06:00
|
|
|
"github.com/stretchr/testify/require"
|
2021-11-01 04:53:33 -05:00
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/plugins"
|
2023-04-12 04:34:16 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
2022-09-14 08:35:35 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/config"
|
2023-02-28 09:10:27 -06:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/log"
|
2022-09-23 07:27:01 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
2023-06-07 04:08:01 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/angulardetector"
|
2023-03-20 08:35:49 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
2021-11-01 04:53:33 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/initializer"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
2023-04-27 10:54:28 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/signature/statickey"
|
2023-03-20 08:35:49 -05:00
|
|
|
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
2021-11-01 04:53:33 -05:00
|
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
|
|
)
|
|
|
|
|
2023-04-27 03:26:15 -05:00
|
|
|
var compareOpts = []cmp.Option{cmpopts.IgnoreFields(plugins.Plugin{}, "client", "log"), fsComparer}
|
2023-03-07 09:47:02 -06:00
|
|
|
|
2023-04-27 03:26:15 -05:00
|
|
|
var fsComparer = cmp.Comparer(func(fs1 plugins.FS, fs2 plugins.FS) bool {
|
|
|
|
fs1Files, err := fs1.Files()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
fs2Files, err := fs2.Files()
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
2023-03-07 09:47:02 -06:00
|
|
|
|
|
|
|
sort.SliceStable(fs1Files, func(i, j int) bool {
|
|
|
|
return fs1Files[i] < fs1Files[j]
|
|
|
|
})
|
|
|
|
|
|
|
|
sort.SliceStable(fs2Files, func(i, j int) bool {
|
|
|
|
return fs2Files[i] < fs2Files[j]
|
|
|
|
})
|
|
|
|
|
|
|
|
return cmp.Equal(fs1Files, fs2Files) && fs1.Base() == fs2.Base()
|
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
|
|
|
|
func TestLoader_Load(t *testing.T) {
|
|
|
|
corePluginDir, err := filepath.Abs("./../../../../public")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of core plugins dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
parentDir, err := filepath.Abs("../")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of current dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
tests := []struct {
|
2022-09-23 07:27:01 -05:00
|
|
|
name string
|
|
|
|
class plugins.Class
|
|
|
|
cfg *config.Cfg
|
|
|
|
pluginPaths []string
|
|
|
|
want []*plugins.Plugin
|
|
|
|
pluginErrors map[string]*plugins.Error
|
2021-11-01 04:53:33 -05:00
|
|
|
}{
|
|
|
|
{
|
2022-09-14 08:35:35 -05:00
|
|
|
name: "Load a Core plugin",
|
|
|
|
class: plugins.Core,
|
|
|
|
cfg: &config.Cfg{},
|
2021-11-01 04:53:33 -05:00
|
|
|
pluginPaths: []string{filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch")},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "cloudwatch",
|
|
|
|
Type: "datasource",
|
|
|
|
Name: "CloudWatch",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Grafana Labs",
|
|
|
|
URL: "https://grafana.com",
|
|
|
|
},
|
|
|
|
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",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Includes: []*plugins.Includes{
|
|
|
|
{Name: "EC2", Path: "dashboards/ec2.json", Type: "dashboard", Role: "Viewer"},
|
|
|
|
{Name: "EBS", Path: "dashboards/EBS.json", Type: "dashboard", Role: "Viewer"},
|
|
|
|
{Name: "Lambda", Path: "dashboards/Lambda.json", Type: "dashboard", Role: "Viewer"},
|
|
|
|
{Name: "Logs", Path: "dashboards/Logs.json", Type: "dashboard", Role: "Viewer"},
|
|
|
|
{Name: "RDS", Path: "dashboards/RDS.json", Type: "dashboard", Role: "Viewer"},
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Category: "cloud",
|
|
|
|
Annotations: true,
|
|
|
|
Metrics: true,
|
|
|
|
Alerting: true,
|
|
|
|
Logs: true,
|
2021-12-22 10:59:26 -06:00
|
|
|
Backend: true,
|
2021-11-01 04:53:33 -05:00
|
|
|
QueryOptions: map[string]bool{"minInterval": true},
|
|
|
|
},
|
2023-03-07 09:47:02 -06:00
|
|
|
Module: "app/plugins/datasource/cloudwatch/module",
|
|
|
|
BaseURL: "public/app/plugins/datasource/cloudwatch",
|
2023-04-27 03:26:15 -05:00
|
|
|
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch")),
|
2022-01-14 06:30:39 -06:00
|
|
|
Signature: plugins.SignatureInternal,
|
|
|
|
Class: plugins.Core,
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-09-14 08:35:35 -05:00
|
|
|
name: "Load a Bundled plugin",
|
|
|
|
class: plugins.Bundled,
|
|
|
|
cfg: &config.Cfg{},
|
2021-11-01 04:53:33 -05:00
|
|
|
pluginPaths: []string{"../testdata/valid-v2-signature"},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
2022-08-22 11:11:45 -05:00
|
|
|
ID: "test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
Type: "datasource",
|
|
|
|
Name: "Test",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Will Browne",
|
|
|
|
URL: "https://willbrowne.com",
|
|
|
|
},
|
|
|
|
Version: "1.0.0",
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-datasource.svg",
|
|
|
|
Large: "public/img/icn-datasource.svg",
|
|
|
|
},
|
|
|
|
Description: "Test",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Executable: "test",
|
|
|
|
Backend: true,
|
|
|
|
State: "alpha",
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Module: "plugins/test-datasource/module",
|
|
|
|
BaseURL: "public/plugins/test-datasource",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/valid-v2-signature/plugin/")),
|
2021-11-01 04:53:33 -05:00
|
|
|
Signature: "valid",
|
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
2022-01-14 06:30:39 -06:00
|
|
|
Class: plugins.Bundled,
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
}, {
|
2022-09-14 08:35:35 -05:00
|
|
|
name: "Load plugin with symbolic links",
|
|
|
|
class: plugins.External,
|
|
|
|
cfg: &config.Cfg{},
|
2021-11-01 04:53:33 -05:00
|
|
|
pluginPaths: []string{"../testdata/symbolic-plugin-dirs"},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "test-app",
|
|
|
|
Type: "app",
|
|
|
|
Name: "Test App",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Test Inc.",
|
|
|
|
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",
|
|
|
|
},
|
|
|
|
Links: []plugins.InfoLink{
|
|
|
|
{Name: "Project site", URL: "http://project.com"},
|
|
|
|
{Name: "License & Terms", URL: "http://license.com"},
|
|
|
|
},
|
|
|
|
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"},
|
|
|
|
},
|
|
|
|
Version: "1.0.0",
|
|
|
|
Updated: "2015-02-10",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "3.x.x",
|
|
|
|
Plugins: []plugins.Dependency{
|
|
|
|
{Type: "datasource", ID: "graphite", Name: "Graphite", Version: "1.0.0"},
|
|
|
|
{Type: "panel", ID: "graph", Name: "Graph", Version: "1.0.0"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Includes: []*plugins.Includes{
|
|
|
|
{
|
|
|
|
Name: "Nginx Connections",
|
|
|
|
Path: "dashboards/connections.json",
|
|
|
|
Type: "dashboard",
|
|
|
|
Role: "Viewer",
|
|
|
|
Slug: "nginx-connections",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "Nginx Memory",
|
|
|
|
Path: "dashboards/memory.json",
|
|
|
|
Type: "dashboard",
|
|
|
|
Role: "Viewer",
|
|
|
|
Slug: "nginx-memory",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "Nginx Panel",
|
|
|
|
Type: "panel",
|
|
|
|
Role: "Viewer",
|
|
|
|
Slug: "nginx-panel"},
|
|
|
|
{
|
|
|
|
Name: "Nginx Datasource",
|
|
|
|
Type: "datasource",
|
|
|
|
Role: "Viewer",
|
|
|
|
Slug: "nginx-datasource",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Class: plugins.External,
|
|
|
|
Module: "plugins/test-app/module",
|
|
|
|
BaseURL: "public/plugins/test-app",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/includes-symlinks")),
|
2021-11-01 04:53:33 -05:00
|
|
|
Signature: "valid",
|
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, {
|
2022-01-14 06:30:39 -06:00
|
|
|
name: "Load an unsigned plugin (development)",
|
|
|
|
class: plugins.External,
|
2022-09-14 08:35:35 -05:00
|
|
|
cfg: &config.Cfg{
|
|
|
|
DevMode: true,
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
2022-08-22 11:11:45 -05:00
|
|
|
ID: "test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
Type: "datasource",
|
|
|
|
Name: "Test",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Grafana Labs",
|
|
|
|
URL: "https://grafana.com",
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-datasource.svg",
|
|
|
|
Large: "public/img/icn-datasource.svg",
|
|
|
|
},
|
|
|
|
Description: "Test",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Backend: true,
|
|
|
|
State: plugins.AlphaRelease,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Class: plugins.External,
|
|
|
|
Module: "plugins/test-datasource/module",
|
|
|
|
BaseURL: "public/plugins/test-datasource",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/unsigned-datasource/plugin")),
|
2021-11-01 04:53:33 -05:00
|
|
|
Signature: "unsigned",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}, {
|
2022-09-14 08:35:35 -05:00
|
|
|
name: "Load an unsigned plugin (production)",
|
|
|
|
class: plugins.External,
|
|
|
|
cfg: &config.Cfg{},
|
2021-11-01 04:53:33 -05:00
|
|
|
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
|
|
|
want: []*plugins.Plugin{},
|
|
|
|
pluginErrors: map[string]*plugins.Error{
|
2022-08-22 11:11:45 -05:00
|
|
|
"test-datasource": {
|
|
|
|
PluginID: "test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
ErrorCode: "signatureMissing",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-01-14 06:30:39 -06:00
|
|
|
name: "Load an unsigned plugin using PluginsAllowUnsigned config (production)",
|
|
|
|
class: plugins.External,
|
2022-09-14 08:35:35 -05:00
|
|
|
cfg: &config.Cfg{
|
2022-08-22 11:11:45 -05:00
|
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
pluginPaths: []string{"../testdata/unsigned-datasource"},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
2022-08-22 11:11:45 -05:00
|
|
|
ID: "test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
Type: "datasource",
|
|
|
|
Name: "Test",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Grafana Labs",
|
|
|
|
URL: "https://grafana.com",
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-datasource.svg",
|
|
|
|
Large: "public/img/icn-datasource.svg",
|
|
|
|
},
|
|
|
|
Description: "Test",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Backend: true,
|
|
|
|
State: plugins.AlphaRelease,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Class: plugins.External,
|
|
|
|
Module: "plugins/test-datasource/module",
|
|
|
|
BaseURL: "public/plugins/test-datasource",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/unsigned-datasource/plugin")),
|
2022-01-14 06:30:39 -06:00
|
|
|
Signature: plugins.SignatureUnsigned,
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-10-21 06:54:55 -05:00
|
|
|
name: "Load a plugin with v1 manifest should return signatureInvalid",
|
2022-09-14 08:35:35 -05:00
|
|
|
class: plugins.External,
|
|
|
|
cfg: &config.Cfg{},
|
2021-11-01 04:53:33 -05:00
|
|
|
pluginPaths: []string{"../testdata/lacking-files"},
|
|
|
|
want: []*plugins.Plugin{},
|
|
|
|
pluginErrors: map[string]*plugins.Error{
|
2022-08-22 11:11:45 -05:00
|
|
|
"test-datasource": {
|
|
|
|
PluginID: "test-datasource",
|
2022-10-21 06:54:55 -05:00
|
|
|
ErrorCode: "signatureInvalid",
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
2022-10-21 06:54:55 -05:00
|
|
|
name: "Load a plugin with v1 manifest using PluginsAllowUnsigned config (production) should return signatureInvali",
|
2022-01-14 06:30:39 -06:00
|
|
|
class: plugins.External,
|
2022-09-14 08:35:35 -05:00
|
|
|
cfg: &config.Cfg{
|
2022-08-22 11:11:45 -05:00
|
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
pluginPaths: []string{"../testdata/lacking-files"},
|
|
|
|
want: []*plugins.Plugin{},
|
|
|
|
pluginErrors: map[string]*plugins.Error{
|
2022-08-22 11:11:45 -05:00
|
|
|
"test-datasource": {
|
|
|
|
PluginID: "test-datasource",
|
2022-10-21 06:54:55 -05:00
|
|
|
ErrorCode: "signatureInvalid",
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-07-15 10:42:20 -05:00
|
|
|
{
|
|
|
|
name: "Load a plugin with manifest which has a file not found in plugin folder",
|
|
|
|
class: plugins.External,
|
2022-09-14 08:35:35 -05:00
|
|
|
cfg: &config.Cfg{
|
2022-08-22 11:11:45 -05:00
|
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
2022-07-15 10:42:20 -05:00
|
|
|
},
|
|
|
|
pluginPaths: []string{"../testdata/invalid-v2-missing-file"},
|
|
|
|
want: []*plugins.Plugin{},
|
|
|
|
pluginErrors: map[string]*plugins.Error{
|
2022-08-22 11:11:45 -05:00
|
|
|
"test-datasource": {
|
|
|
|
PluginID: "test-datasource",
|
2022-07-15 10:42:20 -05:00
|
|
|
ErrorCode: "signatureModified",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Load a plugin with file which is missing from the manifest",
|
|
|
|
class: plugins.External,
|
2022-09-14 08:35:35 -05:00
|
|
|
cfg: &config.Cfg{
|
2022-08-22 11:11:45 -05:00
|
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
2022-07-15 10:42:20 -05:00
|
|
|
},
|
|
|
|
pluginPaths: []string{"../testdata/invalid-v2-extra-file"},
|
|
|
|
want: []*plugins.Plugin{},
|
|
|
|
pluginErrors: map[string]*plugins.Error{
|
2022-08-22 11:11:45 -05:00
|
|
|
"test-datasource": {
|
|
|
|
PluginID: "test-datasource",
|
2022-07-15 10:42:20 -05:00
|
|
|
ErrorCode: "signatureModified",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2022-01-14 06:30:39 -06:00
|
|
|
{
|
|
|
|
name: "Load an app with includes",
|
|
|
|
class: plugins.External,
|
2022-09-14 08:35:35 -05:00
|
|
|
cfg: &config.Cfg{
|
2022-01-14 06:30:39 -06:00
|
|
|
PluginsAllowUnsigned: []string{"test-app"},
|
|
|
|
},
|
|
|
|
pluginPaths: []string{"../testdata/test-app-with-includes"},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{JSONData: plugins.JSONData{
|
|
|
|
ID: "test-app",
|
|
|
|
Type: "app",
|
|
|
|
Name: "Test App",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Test Inc.",
|
|
|
|
URL: "http://test.com",
|
|
|
|
},
|
|
|
|
Description: "Official Grafana Test App & Dashboard bundle",
|
|
|
|
Version: "1.0.0",
|
|
|
|
Links: []plugins.InfoLink{
|
|
|
|
{Name: "Project site", URL: "http://project.com"},
|
|
|
|
{Name: "License & Terms", URL: "http://license.com"},
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-app.svg",
|
|
|
|
Large: "public/img/icn-app.svg",
|
|
|
|
},
|
|
|
|
Updated: "2015-02-10",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaDependency: ">=8.0.0",
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Includes: []*plugins.Includes{
|
2022-04-06 03:50:39 -05:00
|
|
|
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-memory"},
|
2022-01-14 06:30:39 -06:00
|
|
|
{Name: "Root Page (react)", Type: "page", Role: "Viewer", Path: "/a/my-simple-app", DefaultNav: true, AddToNav: true, Slug: "root-page-react"},
|
|
|
|
},
|
|
|
|
Backend: false,
|
|
|
|
},
|
|
|
|
DefaultNavURL: "/plugins/test-app/page/root-page-react",
|
2023-04-27 03:26:15 -05:00
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/test-app-with-includes")),
|
|
|
|
Class: plugins.External,
|
|
|
|
Signature: plugins.SignatureUnsigned,
|
|
|
|
Module: "plugins/test-app/module",
|
|
|
|
BaseURL: "public/plugins/test-app",
|
2022-01-14 06:30:39 -06:00
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
for _, tt := range tests {
|
2022-09-23 07:27:01 -05:00
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(tt.cfg, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(tt.cfg, procPrvdr, &fakes.FakeLicensingService{})
|
|
|
|
})
|
|
|
|
|
2021-11-01 04:53:33 -05:00
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), sources.NewLocalSource(tt.class, tt.pluginPaths))
|
2021-11-01 04:53:33 -05:00
|
|
|
require.NoError(t, err)
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pluginErrs := l.PluginErrors()
|
2022-09-23 07:27:01 -05:00
|
|
|
require.Equal(t, len(tt.pluginErrors), len(pluginErrs))
|
2021-11-01 04:53:33 -05:00
|
|
|
for _, pluginErr := range pluginErrs {
|
2022-09-23 07:27:01 -05:00
|
|
|
require.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr)
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
2022-09-23 07:27:01 -05:00
|
|
|
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, tt.want, reg, procPrvdr, procMgr)
|
2021-11-01 04:53:33 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-04 10:01:49 -05:00
|
|
|
func TestLoader_Load_CustomSource(t *testing.T) {
|
|
|
|
t.Run("Load a plugin", func(t *testing.T) {
|
|
|
|
parentDir, err := filepath.Abs("../")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of current dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
cfg := &config.Cfg{
|
|
|
|
PluginsCDNURLTemplate: "https://cdn.example.com",
|
|
|
|
PluginSettings: setting.PluginSettings{
|
|
|
|
"grafana-worldmap-panel": {"cdn": "true"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
pluginPaths := []string{"../testdata/cdn"}
|
|
|
|
expected := []*plugins.Plugin{{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "grafana-worldmap-panel",
|
|
|
|
Type: "panel",
|
|
|
|
Name: "Worldmap Panel",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Version: "0.3.3",
|
|
|
|
Links: []plugins.InfoLink{
|
|
|
|
{Name: "Project site", URL: "https://github.com/grafana/worldmap-panel"},
|
|
|
|
{Name: "MIT License", URL: "https://github.com/grafana/worldmap-panel/blob/master/LICENSE"},
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
// Path substitution
|
|
|
|
Small: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap_logo.svg",
|
|
|
|
Large: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap_logo.svg",
|
|
|
|
},
|
|
|
|
Screenshots: []plugins.Screenshots{
|
|
|
|
{
|
|
|
|
Name: "World",
|
|
|
|
Path: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap-world.png",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "USA",
|
|
|
|
Path: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap-usa.png",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "Light Theme",
|
|
|
|
Path: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/images/worldmap-light-theme.png",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "3.x.x",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/cdn/plugin")),
|
2023-04-04 10:01:49 -05:00
|
|
|
Class: plugins.Bundled,
|
|
|
|
Signature: plugins.SignatureValid,
|
|
|
|
BaseURL: "plugin-cdn/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel",
|
|
|
|
Module: "plugin-cdn/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/module",
|
|
|
|
}}
|
|
|
|
|
|
|
|
l := newLoader(cfg)
|
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.Bundled
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return pluginPaths
|
|
|
|
},
|
|
|
|
DefaultSignatureFunc: func(ctx context.Context) (plugins.Signature, bool) {
|
|
|
|
return plugins.Signature{
|
|
|
|
Status: plugins.SignatureValid,
|
|
|
|
}, true
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-04-06 03:50:39 -05:00
|
|
|
func TestLoader_setDefaultNavURL(t *testing.T) {
|
|
|
|
t.Run("When including a dashboard with DefaultNav: true", func(t *testing.T) {
|
|
|
|
pluginWithDashboard := &plugins.Plugin{
|
|
|
|
JSONData: plugins.JSONData{Includes: []*plugins.Includes{
|
|
|
|
{
|
|
|
|
Type: "dashboard",
|
|
|
|
DefaultNav: true,
|
|
|
|
UID: "",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
2023-02-28 09:10:27 -06:00
|
|
|
logger := log.NewTestLogger()
|
2022-04-06 03:50:39 -05:00
|
|
|
pluginWithDashboard.SetLogger(logger)
|
|
|
|
|
|
|
|
t.Run("Default nav URL is not set if dashboard UID field not is set", func(t *testing.T) {
|
|
|
|
setDefaultNavURL(pluginWithDashboard)
|
|
|
|
require.Equal(t, "", pluginWithDashboard.DefaultNavURL)
|
2022-05-06 10:44:22 -05:00
|
|
|
require.NotZero(t, logger.WarnLogs.Calls)
|
|
|
|
require.Equal(t, "Included dashboard is missing a UID field", logger.WarnLogs.Message)
|
2022-04-06 03:50:39 -05:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Default nav URL is set if dashboard UID field is set", func(t *testing.T) {
|
|
|
|
pluginWithDashboard.Includes[0].UID = "a1b2c3"
|
|
|
|
|
|
|
|
setDefaultNavURL(pluginWithDashboard)
|
|
|
|
require.Equal(t, "/d/a1b2c3", pluginWithDashboard.DefaultNavURL)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("When including a page with DefaultNav: true", func(t *testing.T) {
|
|
|
|
pluginWithPage := &plugins.Plugin{
|
|
|
|
JSONData: plugins.JSONData{Includes: []*plugins.Includes{
|
|
|
|
{
|
|
|
|
Type: "page",
|
|
|
|
DefaultNav: true,
|
|
|
|
Slug: "testPage",
|
|
|
|
},
|
|
|
|
}},
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("Default nav URL is set using slug", func(t *testing.T) {
|
|
|
|
setDefaultNavURL(pluginWithPage)
|
|
|
|
require.Equal(t, "/plugins/page/testPage", pluginWithPage.DefaultNavURL)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Default nav URL is set using slugified Name field if Slug field is empty", func(t *testing.T) {
|
|
|
|
pluginWithPage.Includes[0].Slug = ""
|
|
|
|
pluginWithPage.Includes[0].Name = "My Test Page"
|
|
|
|
|
|
|
|
setDefaultNavURL(pluginWithPage)
|
|
|
|
require.Equal(t, "/plugins/page/my-test-page", pluginWithPage.DefaultNavURL)
|
|
|
|
})
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-11-01 04:53:33 -05:00
|
|
|
func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
|
|
|
parentDir, err := filepath.Abs("../")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of current dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("Load multiple", func(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
2022-09-14 08:35:35 -05:00
|
|
|
cfg *config.Cfg
|
2021-11-01 04:53:33 -05:00
|
|
|
pluginPaths []string
|
|
|
|
appURL string
|
|
|
|
existingPlugins map[string]struct{}
|
|
|
|
want []*plugins.Plugin
|
|
|
|
pluginErrors map[string]*plugins.Error
|
|
|
|
}{
|
|
|
|
{
|
2022-09-14 08:35:35 -05:00
|
|
|
name: "Load multiple plugins (broken, valid, unsigned)",
|
|
|
|
cfg: &config.Cfg{},
|
2021-11-01 04:53:33 -05:00
|
|
|
appURL: "http://localhost:3000",
|
|
|
|
pluginPaths: []string{
|
|
|
|
"../testdata/invalid-plugin-json", // test-app
|
|
|
|
"../testdata/valid-v2-pvt-signature", // test
|
|
|
|
"../testdata/unsigned-panel", // test-panel
|
|
|
|
},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
2022-08-22 11:11:45 -05:00
|
|
|
ID: "test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
Type: "datasource",
|
|
|
|
Name: "Test",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Will Browne",
|
|
|
|
URL: "https://willbrowne.com",
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-datasource.svg",
|
|
|
|
Large: "public/img/icn-datasource.svg",
|
|
|
|
},
|
|
|
|
Description: "Test",
|
|
|
|
Version: "1.0.0",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Backend: true,
|
|
|
|
Executable: "test",
|
|
|
|
State: plugins.AlphaRelease,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Class: plugins.External,
|
|
|
|
Module: "plugins/test-datasource/module",
|
|
|
|
BaseURL: "public/plugins/test-datasource",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "testdata/valid-v2-pvt-signature/plugin")),
|
2021-11-01 04:53:33 -05:00
|
|
|
Signature: "valid",
|
|
|
|
SignatureType: plugins.PrivateSignature,
|
|
|
|
SignatureOrg: "Will Browne",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
pluginErrors: map[string]*plugins.Error{
|
2022-07-04 06:03:50 -05:00
|
|
|
"test-panel": {
|
|
|
|
PluginID: "test-panel",
|
2021-11-01 04:53:33 -05:00
|
|
|
ErrorCode: "signatureMissing",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
2022-09-23 07:27:01 -05:00
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(tt.cfg, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(tt.cfg, procPrvdr, fakes.NewFakeLicensingService())
|
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
origAppURL := setting.AppUrl
|
|
|
|
t.Cleanup(func() {
|
|
|
|
setting.AppUrl = origAppURL
|
|
|
|
})
|
|
|
|
setting.AppUrl = tt.appURL
|
|
|
|
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return tt.pluginPaths
|
|
|
|
},
|
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
require.NoError(t, err)
|
|
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
|
|
return got[i].ID < got[j].ID
|
|
|
|
})
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
2022-07-04 06:03:50 -05:00
|
|
|
pluginErrs := l.PluginErrors()
|
|
|
|
require.Equal(t, len(tt.pluginErrors), len(pluginErrs))
|
|
|
|
for _, pluginErr := range pluginErrs {
|
|
|
|
require.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr)
|
|
|
|
}
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, tt.want, reg, procPrvdr, procMgr)
|
2021-11-01 04:53:33 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2022-11-07 04:30:45 -06:00
|
|
|
func TestLoader_Load_RBACReady(t *testing.T) {
|
|
|
|
pluginDir, err := filepath.Abs("../testdata/test-app-with-roles")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of current dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
cfg *config.Cfg
|
|
|
|
pluginPaths []string
|
|
|
|
appURL string
|
|
|
|
existingPlugins map[string]struct{}
|
|
|
|
want []*plugins.Plugin
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Load plugin defining one RBAC role",
|
|
|
|
cfg: &config.Cfg{},
|
|
|
|
appURL: "http://localhost:3000",
|
|
|
|
pluginPaths: []string{"../testdata/test-app-with-roles"},
|
|
|
|
want: []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "test-app",
|
|
|
|
Type: "app",
|
|
|
|
Name: "Test App",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Test Inc.",
|
|
|
|
URL: "http://test.com",
|
|
|
|
},
|
|
|
|
Description: "Test App",
|
|
|
|
Version: "1.0.0",
|
|
|
|
Links: []plugins.InfoLink{},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-app.svg",
|
|
|
|
Large: "public/img/icn-app.svg",
|
|
|
|
},
|
|
|
|
Updated: "2015-02-10",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
GrafanaDependency: ">=8.0.0",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Includes: []*plugins.Includes{},
|
|
|
|
Roles: []plugins.RoleRegistration{
|
|
|
|
{
|
|
|
|
Role: plugins.Role{
|
2022-11-15 02:51:40 -06:00
|
|
|
Name: "Reader",
|
2022-11-07 04:30:45 -06:00
|
|
|
Description: "View everything in the test-app plugin",
|
|
|
|
Permissions: []plugins.Permission{
|
|
|
|
{Action: "plugins.app:access", Scope: "plugins.app:id:test-app"},
|
|
|
|
{Action: "test-app.resource:read", Scope: "resources:*"},
|
|
|
|
{Action: "test-app.otherresource:toggle"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Grants: []string{"Admin"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Backend: false,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
FS: mustNewStaticFSForTests(t, pluginDir),
|
2022-11-07 04:30:45 -06:00
|
|
|
Class: plugins.External,
|
|
|
|
Signature: plugins.SignatureValid,
|
|
|
|
SignatureType: plugins.PrivateSignature,
|
|
|
|
SignatureOrg: "gabrielmabille",
|
|
|
|
Module: "plugins/test-app/module",
|
|
|
|
BaseURL: "public/plugins/test-app",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
origAppURL := setting.AppUrl
|
|
|
|
t.Cleanup(func() {
|
|
|
|
setting.AppUrl = origAppURL
|
|
|
|
})
|
|
|
|
setting.AppUrl = "http://localhost:3000"
|
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(tt.cfg, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(tt.cfg, procPrvdr, fakes.NewFakeLicensingService())
|
|
|
|
})
|
|
|
|
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return tt.pluginPaths
|
|
|
|
},
|
|
|
|
})
|
2022-11-07 04:30:45 -06:00
|
|
|
require.NoError(t, err)
|
|
|
|
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
2022-11-07 04:30:45 -06:00
|
|
|
}
|
|
|
|
pluginErrs := l.PluginErrors()
|
|
|
|
require.Len(t, pluginErrs, 0)
|
|
|
|
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, tt.want, reg, procPrvdr, procMgr)
|
2022-11-07 04:30:45 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
func TestLoader_Load_Signature_RootURL(t *testing.T) {
|
2021-11-01 04:53:33 -05:00
|
|
|
const defaultAppURL = "http://localhost:3000/grafana"
|
|
|
|
|
|
|
|
parentDir, err := filepath.Abs("../")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of current dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("Private signature verification ignores trailing slash in root URL", func(t *testing.T) {
|
|
|
|
origAppURL := setting.AppUrl
|
|
|
|
origAppSubURL := setting.AppSubUrl
|
|
|
|
t.Cleanup(func() {
|
|
|
|
setting.AppUrl = origAppURL
|
|
|
|
setting.AppSubUrl = origAppSubURL
|
|
|
|
})
|
|
|
|
setting.AppUrl = defaultAppURL
|
|
|
|
|
|
|
|
paths := []string{"../testdata/valid-v2-pvt-signature-root-url-uri"}
|
|
|
|
|
|
|
|
expected := []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
2022-08-22 11:11:45 -05:00
|
|
|
ID: "test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
Type: "datasource",
|
|
|
|
Name: "Test",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{Name: "Will Browne", URL: "https://willbrowne.com"},
|
|
|
|
Description: "Test",
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-datasource.svg",
|
|
|
|
Large: "public/img/icn-datasource.svg",
|
|
|
|
},
|
|
|
|
Version: "1.0.0",
|
|
|
|
},
|
|
|
|
State: plugins.AlphaRelease,
|
|
|
|
Dependencies: plugins.Dependencies{GrafanaVersion: "*", Plugins: []plugins.Dependency{}},
|
|
|
|
Backend: true,
|
|
|
|
Executable: "test",
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(parentDir, "/testdata/valid-v2-pvt-signature-root-url-uri/plugin")),
|
2022-01-14 06:30:39 -06:00
|
|
|
Class: plugins.External,
|
|
|
|
Signature: plugins.SignatureValid,
|
|
|
|
SignatureType: plugins.PrivateSignature,
|
2021-11-01 04:53:33 -05:00
|
|
|
SignatureOrg: "Will Browne",
|
2022-08-22 11:11:45 -05:00
|
|
|
Module: "plugins/test-datasource/module",
|
|
|
|
BaseURL: "public/plugins/test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
|
|
|
})
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return paths
|
|
|
|
},
|
|
|
|
})
|
2022-09-23 07:27:01 -05:00
|
|
|
require.NoError(t, err)
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
2021-11-01 04:53:33 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
|
|
|
t.Run("Load duplicate plugin folders", func(t *testing.T) {
|
|
|
|
pluginDir, err := filepath.Abs("../testdata/test-app")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of plugin dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
expected := []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "test-app",
|
|
|
|
Type: "app",
|
|
|
|
Name: "Test App",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Test Inc.",
|
|
|
|
URL: "http://test.com",
|
|
|
|
},
|
|
|
|
Description: "Official Grafana Test App & Dashboard bundle",
|
|
|
|
Version: "1.0.0",
|
|
|
|
Links: []plugins.InfoLink{
|
|
|
|
{Name: "Project site", URL: "http://project.com"},
|
|
|
|
{Name: "License & Terms", URL: "http://license.com"},
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/plugins/test-app/img/logo_small.png",
|
|
|
|
Large: "public/plugins/test-app/img/logo_large.png",
|
|
|
|
},
|
|
|
|
Screenshots: []plugins.Screenshots{
|
|
|
|
{Path: "public/plugins/test-app/img/screenshot1.png", Name: "img1"},
|
|
|
|
{Path: "public/plugins/test-app/img/screenshot2.png", Name: "img2"},
|
|
|
|
},
|
|
|
|
Updated: "2015-02-10",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "3.x.x",
|
|
|
|
Plugins: []plugins.Dependency{
|
|
|
|
{Type: "datasource", ID: "graphite", Name: "Graphite", Version: "1.0.0"},
|
|
|
|
{Type: "panel", ID: "graph", Name: "Graph", Version: "1.0.0"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Includes: []*plugins.Includes{
|
|
|
|
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-connections"},
|
|
|
|
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-memory"},
|
|
|
|
{Name: "Nginx Panel", Type: "panel", Role: "Viewer", Slug: "nginx-panel"},
|
|
|
|
{Name: "Nginx Datasource", Type: "datasource", Role: "Viewer", Slug: "nginx-datasource"},
|
|
|
|
},
|
|
|
|
Backend: false,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
FS: mustNewStaticFSForTests(t, pluginDir),
|
2021-11-01 04:53:33 -05:00
|
|
|
Class: plugins.External,
|
|
|
|
Signature: plugins.SignatureValid,
|
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
|
|
|
Module: "plugins/test-app/module",
|
|
|
|
BaseURL: "public/plugins/test-app",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
|
|
|
})
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return []string{pluginDir, pluginDir}
|
|
|
|
},
|
|
|
|
})
|
2022-09-23 07:27:01 -05:00
|
|
|
require.NoError(t, err)
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
2022-09-23 07:27:01 -05:00
|
|
|
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
2021-11-01 04:53:33 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-04-12 04:34:16 -05:00
|
|
|
func TestLoader_Load_SkipUninitializedPlugins(t *testing.T) {
|
|
|
|
t.Run("Load duplicate plugin folders", func(t *testing.T) {
|
|
|
|
pluginDir1, err := filepath.Abs("../testdata/test-app")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of plugin dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pluginDir2, err := filepath.Abs("../testdata/valid-v2-signature")
|
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of plugin dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
expected := []*plugins.Plugin{
|
|
|
|
{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "test-app",
|
|
|
|
Type: "app",
|
|
|
|
Name: "Test App",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Test Inc.",
|
|
|
|
URL: "http://test.com",
|
|
|
|
},
|
|
|
|
Description: "Official Grafana Test App & Dashboard bundle",
|
|
|
|
Version: "1.0.0",
|
|
|
|
Links: []plugins.InfoLink{
|
|
|
|
{Name: "Project site", URL: "http://project.com"},
|
|
|
|
{Name: "License & Terms", URL: "http://license.com"},
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/plugins/test-app/img/logo_small.png",
|
|
|
|
Large: "public/plugins/test-app/img/logo_large.png",
|
|
|
|
},
|
|
|
|
Screenshots: []plugins.Screenshots{
|
|
|
|
{Path: "public/plugins/test-app/img/screenshot1.png", Name: "img1"},
|
|
|
|
{Path: "public/plugins/test-app/img/screenshot2.png", Name: "img2"},
|
|
|
|
},
|
|
|
|
Updated: "2015-02-10",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "3.x.x",
|
|
|
|
Plugins: []plugins.Dependency{
|
|
|
|
{Type: "datasource", ID: "graphite", Name: "Graphite", Version: "1.0.0"},
|
|
|
|
{Type: "panel", ID: "graph", Name: "Graph", Version: "1.0.0"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Includes: []*plugins.Includes{
|
|
|
|
{Name: "Nginx Connections", Path: "dashboards/connections.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-connections"},
|
|
|
|
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: "Viewer", Slug: "nginx-memory"},
|
|
|
|
{Name: "Nginx Panel", Type: "panel", Role: "Viewer", Slug: "nginx-panel"},
|
|
|
|
{Name: "Nginx Datasource", Type: "datasource", Role: "Viewer", Slug: "nginx-datasource"},
|
|
|
|
},
|
|
|
|
Backend: false,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
FS: mustNewStaticFSForTests(t, pluginDir1),
|
2023-04-12 04:34:16 -05:00
|
|
|
Class: plugins.External,
|
|
|
|
Signature: plugins.SignatureValid,
|
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
|
|
|
Module: "plugins/test-app/module",
|
|
|
|
BaseURL: "public/plugins/test-app",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
// Cause an initialization error
|
|
|
|
procPrvdr.BackendFactoryFunc = func(ctx context.Context, p *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
|
|
|
return func(pluginID string, _ log.Logger, _ []string) (backendplugin.Plugin, error) {
|
|
|
|
if pluginID == "test-datasource" {
|
|
|
|
return nil, fmt.Errorf("failed to initialize")
|
|
|
|
}
|
|
|
|
return &fakes.FakePluginClient{}, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
|
|
|
})
|
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return []string{pluginDir1, pluginDir2}
|
|
|
|
},
|
|
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
|
|
}
|
|
|
|
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
2023-04-12 04:34:16 -05:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2023-06-07 04:08:01 -05:00
|
|
|
func TestLoader_Load_Angular(t *testing.T) {
|
|
|
|
fakePluginSource := &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return []string{"../testdata/valid-v2-signature"}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
for _, cfgTc := range []struct {
|
|
|
|
name string
|
|
|
|
cfg *config.Cfg
|
|
|
|
}{
|
|
|
|
{name: "angular support enabled", cfg: &config.Cfg{AngularSupportEnabled: true}},
|
|
|
|
{name: "angular support disabled", cfg: &config.Cfg{AngularSupportEnabled: false}},
|
|
|
|
} {
|
|
|
|
t.Run(cfgTc.name, func(t *testing.T) {
|
|
|
|
for _, tc := range []struct {
|
|
|
|
name string
|
|
|
|
angularInspector angulardetector.Inspector
|
|
|
|
shouldLoad bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "angular plugin",
|
|
|
|
angularInspector: angulardetector.AlwaysAngularFakeInspector,
|
|
|
|
// angular plugins should load only if allowed by the cfg
|
|
|
|
shouldLoad: cfgTc.cfg.AngularSupportEnabled,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "non angular plugin",
|
|
|
|
angularInspector: angulardetector.NeverAngularFakeInspector,
|
|
|
|
// non-angular plugins should always load
|
|
|
|
shouldLoad: true,
|
|
|
|
},
|
|
|
|
} {
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
l := newLoader(cfgTc.cfg, func(l *Loader) {
|
|
|
|
l.angularInspector = tc.angularInspector
|
|
|
|
})
|
|
|
|
p, err := l.Load(context.Background(), fakePluginSource)
|
|
|
|
require.NoError(t, err)
|
|
|
|
if tc.shouldLoad {
|
|
|
|
require.Len(t, p, 1, "plugin should have been loaded")
|
|
|
|
} else {
|
|
|
|
require.Empty(t, p, "plugin shouldn't have been loaded")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
func TestLoader_Load_NestedPlugins(t *testing.T) {
|
2022-01-25 09:05:12 -06:00
|
|
|
rootDir, err := filepath.Abs("../")
|
2021-11-01 04:53:33 -05:00
|
|
|
if err != nil {
|
|
|
|
t.Errorf("could not construct absolute path of root dir")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
parent := &plugins.Plugin{
|
|
|
|
JSONData: plugins.JSONData{
|
2022-08-22 11:11:45 -05:00
|
|
|
ID: "test-datasource",
|
2021-11-01 04:53:33 -05:00
|
|
|
Type: "datasource",
|
|
|
|
Name: "Parent",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Grafana Labs",
|
|
|
|
URL: "http://grafana.com",
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-datasource.svg",
|
|
|
|
Large: "public/img/icn-datasource.svg",
|
|
|
|
},
|
|
|
|
Description: "Parent plugin",
|
|
|
|
Version: "1.0.0",
|
|
|
|
Updated: "2020-10-20",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Backend: true,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Module: "plugins/test-datasource/module",
|
|
|
|
BaseURL: "public/plugins/test-datasource",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(rootDir, "testdata/nested-plugins/parent")),
|
2022-01-14 06:30:39 -06:00
|
|
|
Signature: plugins.SignatureValid,
|
2021-11-01 04:53:33 -05:00
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
2022-01-14 06:30:39 -06:00
|
|
|
Class: plugins.External,
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
child := &plugins.Plugin{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "test-panel",
|
|
|
|
Type: "panel",
|
|
|
|
Name: "Child",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Grafana Labs",
|
|
|
|
URL: "http://grafana.com",
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/img/icn-panel.svg",
|
|
|
|
Large: "public/img/icn-panel.svg",
|
|
|
|
},
|
|
|
|
Description: "Child plugin",
|
|
|
|
Version: "1.0.1",
|
|
|
|
Updated: "2020-10-30",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Module: "plugins/test-panel/module",
|
|
|
|
BaseURL: "public/plugins/test-panel",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(rootDir, "testdata/nested-plugins/parent/nested")),
|
2022-01-14 06:30:39 -06:00
|
|
|
Signature: plugins.SignatureValid,
|
2021-11-01 04:53:33 -05:00
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
2022-01-14 06:30:39 -06:00
|
|
|
Class: plugins.External,
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
parent.Children = []*plugins.Plugin{child}
|
|
|
|
child.Parent = parent
|
|
|
|
|
|
|
|
t.Run("Load nested External plugins", func(t *testing.T) {
|
2022-09-23 07:27:01 -05:00
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return []string{"../testdata/nested-plugins"}
|
|
|
|
},
|
|
|
|
})
|
2022-09-23 07:27:01 -05:00
|
|
|
require.NoError(t, err)
|
2021-11-01 04:53:33 -05:00
|
|
|
|
|
|
|
// to ensure we can compare with expected
|
|
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
|
|
return got[i].ID < got[j].ID
|
|
|
|
})
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
expected := []*plugins.Plugin{parent, child}
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
t.Run("Load will exclude plugins that already exist", func(t *testing.T) {
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return []string{"../testdata/nested-plugins"}
|
|
|
|
},
|
|
|
|
})
|
2022-09-23 07:27:01 -05:00
|
|
|
require.NoError(t, err)
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
// to ensure we can compare with expected
|
|
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
|
|
return got[i].ID < got[j].ID
|
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, []*plugins.Plugin{}, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
2022-09-23 07:27:01 -05:00
|
|
|
}
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
2022-09-23 07:27:01 -05:00
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
})
|
2022-01-21 04:00:20 -06:00
|
|
|
|
|
|
|
t.Run("Plugin child field `IncludedInAppID` is set to parent app's plugin ID", func(t *testing.T) {
|
|
|
|
parent := &plugins.Plugin{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "myorgid-simple-app",
|
|
|
|
Type: "app",
|
|
|
|
Name: "Simple App",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Your Name",
|
|
|
|
},
|
|
|
|
Links: []plugins.InfoLink{
|
|
|
|
{Name: "Website", URL: "https://github.com/grafana/grafana-starter-app"},
|
|
|
|
{Name: "License", URL: "https://github.com/grafana/grafana-starter-app/blob/master/LICENSE"},
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/plugins/myorgid-simple-app/img/logo.svg",
|
|
|
|
Large: "public/plugins/myorgid-simple-app/img/logo.svg",
|
|
|
|
},
|
|
|
|
Screenshots: []plugins.Screenshots{},
|
|
|
|
Description: "Grafana App Plugin Template",
|
|
|
|
Version: "%VERSION%",
|
|
|
|
Updated: "%TODAY%",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaVersion: "7.0.0",
|
|
|
|
GrafanaDependency: ">=7.0.0",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
Includes: []*plugins.Includes{
|
|
|
|
{
|
|
|
|
Name: "Root Page (react)",
|
|
|
|
Path: "/a/myorgid-simple-app",
|
|
|
|
Type: "page",
|
|
|
|
Role: "Viewer",
|
|
|
|
AddToNav: true,
|
|
|
|
DefaultNav: true,
|
|
|
|
Slug: "root-page-react",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "Root Page (Tab B)",
|
|
|
|
Path: "/a/myorgid-simple-app/?tab=b",
|
|
|
|
Type: "page",
|
|
|
|
Role: "Viewer",
|
|
|
|
AddToNav: true,
|
|
|
|
Slug: "root-page-tab-b",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "React Config",
|
|
|
|
Path: "/plugins/myorgid-simple-app/?page=page2",
|
|
|
|
Type: "page",
|
|
|
|
Role: "Admin",
|
|
|
|
AddToNav: true,
|
|
|
|
Slug: "react-config",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "Streaming Example",
|
|
|
|
Path: "dashboards/streaming.json",
|
|
|
|
Type: "dashboard",
|
|
|
|
Role: "Viewer",
|
|
|
|
Slug: "streaming-example",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
Name: "Lots of Stats",
|
|
|
|
Path: "dashboards/stats.json",
|
|
|
|
Type: "dashboard",
|
|
|
|
Role: "Viewer",
|
|
|
|
Slug: "lots-of-stats",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Backend: false,
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Module: "plugins/myorgid-simple-app/module",
|
|
|
|
BaseURL: "public/plugins/myorgid-simple-app",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(rootDir, "testdata/app-with-child/dist")),
|
2022-01-21 04:00:20 -06:00
|
|
|
DefaultNavURL: "/plugins/myorgid-simple-app/page/root-page-react",
|
|
|
|
Signature: plugins.SignatureValid,
|
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
|
|
|
Class: plugins.External,
|
|
|
|
}
|
|
|
|
|
|
|
|
child := &plugins.Plugin{
|
|
|
|
JSONData: plugins.JSONData{
|
|
|
|
ID: "myorgid-simple-panel",
|
|
|
|
Type: "panel",
|
|
|
|
Name: "Grafana Panel Plugin Template",
|
|
|
|
Info: plugins.Info{
|
|
|
|
Author: plugins.InfoLink{
|
|
|
|
Name: "Your Name",
|
|
|
|
},
|
|
|
|
Links: []plugins.InfoLink{
|
|
|
|
{Name: "Website", URL: "https://github.com/grafana/grafana-starter-panel"},
|
|
|
|
{Name: "License", URL: "https://github.com/grafana/grafana-starter-panel/blob/master/LICENSE"},
|
|
|
|
},
|
|
|
|
Logos: plugins.Logos{
|
|
|
|
Small: "public/plugins/myorgid-simple-panel/img/logo.svg",
|
|
|
|
Large: "public/plugins/myorgid-simple-panel/img/logo.svg",
|
|
|
|
},
|
|
|
|
Screenshots: []plugins.Screenshots{},
|
|
|
|
Description: "Grafana Panel Plugin Template",
|
|
|
|
Version: "%VERSION%",
|
|
|
|
Updated: "%TODAY%",
|
|
|
|
},
|
|
|
|
Dependencies: plugins.Dependencies{
|
|
|
|
GrafanaDependency: ">=7.0.0",
|
|
|
|
GrafanaVersion: "*",
|
|
|
|
Plugins: []plugins.Dependency{},
|
|
|
|
},
|
|
|
|
},
|
2023-04-27 03:26:15 -05:00
|
|
|
Module: "plugins/myorgid-simple-app/child/module",
|
|
|
|
BaseURL: "public/plugins/myorgid-simple-app",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(rootDir, "testdata/app-with-child/dist/child")),
|
2022-01-21 04:00:20 -06:00
|
|
|
IncludedInAppID: parent.ID,
|
|
|
|
Signature: plugins.SignatureValid,
|
|
|
|
SignatureType: plugins.GrafanaSignature,
|
|
|
|
SignatureOrg: "Grafana Labs",
|
|
|
|
Class: plugins.External,
|
|
|
|
}
|
|
|
|
|
|
|
|
parent.Children = []*plugins.Plugin{child}
|
|
|
|
child.Parent = parent
|
|
|
|
expected := []*plugins.Plugin{parent, child}
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
|
|
l := newLoader(&config.Cfg{}, func(l *Loader) {
|
|
|
|
l.pluginRegistry = reg
|
|
|
|
l.processManager = procMgr
|
|
|
|
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
|
|
|
})
|
2023-03-20 08:35:49 -05:00
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
|
|
return plugins.External
|
|
|
|
},
|
|
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
|
|
return []string{"../testdata/app-with-child"}
|
|
|
|
},
|
|
|
|
})
|
2022-09-23 07:27:01 -05:00
|
|
|
require.NoError(t, err)
|
2022-01-21 04:00:20 -06:00
|
|
|
|
|
|
|
// to ensure we can compare with expected
|
|
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
|
|
return got[i].ID < got[j].ID
|
|
|
|
})
|
|
|
|
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
2022-01-21 04:00:20 -06:00
|
|
|
}
|
2022-01-25 09:05:12 -06:00
|
|
|
|
2023-04-20 04:52:59 -05:00
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
2022-01-21 04:00:20 -06:00
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
|
2022-01-14 06:30:39 -06:00
|
|
|
func Test_setPathsBasedOnApp(t *testing.T) {
|
|
|
|
t.Run("When setting paths based on core plugin on Windows", func(t *testing.T) {
|
|
|
|
child := &plugins.Plugin{
|
2023-03-07 09:47:02 -06:00
|
|
|
FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource"),
|
2022-01-14 06:30:39 -06:00
|
|
|
}
|
|
|
|
parent := &plugins.Plugin{
|
|
|
|
JSONData: plugins.JSONData{
|
2022-01-25 09:05:12 -06:00
|
|
|
Type: plugins.App,
|
2022-08-22 11:11:45 -05:00
|
|
|
ID: "testdata-app",
|
2021-11-01 04:53:33 -05:00
|
|
|
},
|
2023-03-07 09:47:02 -06:00
|
|
|
Class: plugins.Core,
|
|
|
|
FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app"),
|
|
|
|
BaseURL: "public/app/plugins/app/testdata-app",
|
2022-01-14 06:30:39 -06:00
|
|
|
}
|
|
|
|
|
2022-11-28 06:43:14 -06:00
|
|
|
configureAppChildPlugin(parent, child)
|
2022-01-14 06:30:39 -06:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
require.Equal(t, "app/plugins/app/testdata-app/datasources/datasource/module", child.Module)
|
|
|
|
require.Equal(t, "testdata-app", child.IncludedInAppID)
|
|
|
|
require.Equal(t, "public/app/plugins/app/testdata-app", child.BaseURL)
|
2022-01-14 06:30:39 -06:00
|
|
|
})
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
func newLoader(cfg *config.Cfg, cbs ...func(loader *Loader)) *Loader {
|
|
|
|
l := New(cfg, &fakes.FakeLicensingService{}, signature.NewUnsignedAuthorizer(cfg), fakes.NewFakePluginRegistry(),
|
2023-04-20 04:52:59 -05:00
|
|
|
fakes.NewFakeBackendProcessProvider(), fakes.NewFakeProcessManager(), fakes.NewFakeRoleRegistry(),
|
2023-04-27 03:26:15 -05:00
|
|
|
assetpath.ProvideService(pluginscdn.ProvideService(cfg)), finder.NewLocalFinder(cfg),
|
2023-06-07 04:08:01 -05:00
|
|
|
signature.ProvideService(cfg, statickey.New()), angulardetector.NewDefaultPatternsListInspector())
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
for _, cb := range cbs {
|
|
|
|
cb(l)
|
|
|
|
}
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
return l
|
2021-11-01 04:53:33 -05:00
|
|
|
}
|
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
func verifyState(t *testing.T, ps []*plugins.Plugin, reg *fakes.FakePluginRegistry,
|
2023-04-20 04:52:59 -05:00
|
|
|
procPrvdr *fakes.FakeBackendProcessProvider, procMngr *fakes.FakeProcessManager) {
|
2022-09-23 07:27:01 -05:00
|
|
|
t.Helper()
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
for _, p := range ps {
|
2023-03-07 09:47:02 -06:00
|
|
|
if !cmp.Equal(p, reg.Store[p.ID], compareOpts...) {
|
|
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(p, reg.Store[p.ID], compareOpts...))
|
2022-09-23 07:27:01 -05:00
|
|
|
}
|
2021-11-01 04:53:33 -05:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
if p.Backend {
|
|
|
|
require.Equal(t, 1, procPrvdr.Requested[p.ID])
|
|
|
|
require.Equal(t, 1, procPrvdr.Invoked[p.ID])
|
|
|
|
} else {
|
|
|
|
require.Zero(t, procPrvdr.Requested[p.ID])
|
|
|
|
require.Zero(t, procPrvdr.Invoked[p.ID])
|
|
|
|
}
|
2021-11-23 10:09:52 -06:00
|
|
|
|
2022-09-23 07:27:01 -05:00
|
|
|
require.Equal(t, 1, procMngr.Started[p.ID])
|
|
|
|
require.Zero(t, procMngr.Stopped[p.ID])
|
|
|
|
}
|
2022-01-07 14:11:23 -06:00
|
|
|
}
|
2023-03-07 09:47:02 -06:00
|
|
|
|
2023-04-27 03:26:15 -05:00
|
|
|
func mustNewStaticFSForTests(t *testing.T, dir string) plugins.FS {
|
|
|
|
sfs, err := plugins.NewStaticFS(plugins.NewLocalFS(dir))
|
|
|
|
require.NoError(t, err)
|
|
|
|
return sfs
|
2023-03-07 09:47:02 -06:00
|
|
|
}
|