mirror of
https://github.com/grafana/grafana.git
synced 2024-11-29 20:24:18 -06:00
f76b9f266e
* Add plugins.hide_angular_deprecation config ini * Add more tests * Remove the ability to use [plugins.*] hide_angular_deprecation
1710 lines
59 KiB
Go
1710 lines
59 KiB
Go
package loader
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"path/filepath"
|
|
"sort"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/go-cmp/cmp/cmpopts"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/plugins/auth"
|
|
"github.com/grafana/grafana/pkg/plugins/backendplugin"
|
|
"github.com/grafana/grafana/pkg/plugins/config"
|
|
"github.com/grafana/grafana/pkg/plugins/log"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/angular/angularinspector"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/loader/finder"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/process"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/sources"
|
|
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
|
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pipeline"
|
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
var compareOpts = []cmp.Option{cmpopts.IgnoreFields(plugins.Plugin{}, "client", "log", "mu"), fsComparer}
|
|
|
|
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)
|
|
}
|
|
|
|
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()
|
|
})
|
|
|
|
func TestLoader_Load(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
class plugins.Class
|
|
cfg *config.Cfg
|
|
pluginPaths []string
|
|
want []*plugins.Plugin
|
|
pluginErrors map[string]*plugins.SignatureError
|
|
}{
|
|
{
|
|
name: "Load a Core plugin",
|
|
class: plugins.ClassCore,
|
|
cfg: &config.Cfg{Features: featuremgmt.WithFeatures()},
|
|
pluginPaths: []string{filepath.Join(corePluginDir(t), "app/plugins/datasource/cloudwatch")},
|
|
want: []*plugins.Plugin{
|
|
{
|
|
JSONData: plugins.JSONData{
|
|
ID: "cloudwatch",
|
|
Type: plugins.TypeDataSource,
|
|
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,
|
|
Backend: true,
|
|
QueryOptions: map[string]bool{"minInterval": true},
|
|
},
|
|
Module: "core:plugin/cloudwatch",
|
|
BaseURL: "/public/app/plugins/datasource/cloudwatch",
|
|
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(corePluginDir(t), "app/plugins/datasource/cloudwatch")),
|
|
Signature: plugins.SignatureStatusInternal,
|
|
Class: plugins.ClassCore,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Load a Bundled plugin",
|
|
class: plugins.ClassBundled,
|
|
cfg: &config.Cfg{Features: featuremgmt.WithFeatures()},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "valid-v2-signature")},
|
|
want: []*plugins.Plugin{
|
|
{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-datasource",
|
|
Type: plugins.TypeDataSource,
|
|
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",
|
|
},
|
|
Module: "/public/plugins/test-datasource/module.js",
|
|
BaseURL: "/public/plugins/test-datasource",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "valid-v2-signature/plugin/")),
|
|
Signature: "valid",
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
Class: plugins.ClassBundled,
|
|
},
|
|
},
|
|
}, {
|
|
name: "Load plugin with symbolic links",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{Features: featuremgmt.WithFeatures()},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "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: org.RoleViewer,
|
|
Slug: "nginx-connections",
|
|
},
|
|
{
|
|
Name: "Nginx Memory",
|
|
Path: "dashboards/memory.json",
|
|
Type: "dashboard",
|
|
Role: org.RoleViewer,
|
|
Slug: "nginx-memory",
|
|
},
|
|
{
|
|
Name: "Nginx Panel",
|
|
Type: string(plugins.TypePanel),
|
|
Role: org.RoleViewer,
|
|
Slug: "nginx-panel"},
|
|
{
|
|
Name: "Nginx Datasource",
|
|
Type: string(plugins.TypeDataSource),
|
|
Role: org.RoleViewer,
|
|
Slug: "nginx-datasource",
|
|
},
|
|
},
|
|
},
|
|
Class: plugins.ClassExternal,
|
|
Module: "/public/plugins/test-app/module.js",
|
|
BaseURL: "/public/plugins/test-app",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "includes-symlinks")),
|
|
Signature: "valid",
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
},
|
|
},
|
|
}, {
|
|
name: "Load an unsigned plugin (development)",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{
|
|
DevMode: true,
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "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: "/public/img/icn-datasource.svg",
|
|
Large: "/public/img/icn-datasource.svg",
|
|
},
|
|
Description: "Test",
|
|
},
|
|
Dependencies: plugins.Dependencies{
|
|
GrafanaVersion: "*",
|
|
Plugins: []plugins.Dependency{},
|
|
},
|
|
Backend: true,
|
|
State: plugins.ReleaseStateAlpha,
|
|
},
|
|
Class: plugins.ClassExternal,
|
|
Module: "/public/plugins/test-datasource/module.js",
|
|
BaseURL: "/public/plugins/test-datasource",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "unsigned-datasource/plugin")),
|
|
Signature: "unsigned",
|
|
},
|
|
},
|
|
}, {
|
|
name: "Load an unsigned plugin (production)",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{Features: featuremgmt.WithFeatures()},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "unsigned-datasource")},
|
|
want: []*plugins.Plugin{},
|
|
pluginErrors: map[string]*plugins.SignatureError{
|
|
"test-datasource": {
|
|
PluginID: "test-datasource",
|
|
SignatureStatus: plugins.SignatureStatusUnsigned,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Load an unsigned plugin using PluginsAllowUnsigned config (production)",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "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: "/public/img/icn-datasource.svg",
|
|
Large: "/public/img/icn-datasource.svg",
|
|
},
|
|
Description: "Test",
|
|
},
|
|
Dependencies: plugins.Dependencies{
|
|
GrafanaVersion: "*",
|
|
Plugins: []plugins.Dependency{},
|
|
},
|
|
Backend: true,
|
|
State: plugins.ReleaseStateAlpha,
|
|
},
|
|
Class: plugins.ClassExternal,
|
|
Module: "/public/plugins/test-datasource/module.js",
|
|
BaseURL: "/public/plugins/test-datasource",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "unsigned-datasource/plugin")),
|
|
Signature: plugins.SignatureStatusUnsigned,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Load a plugin with v1 manifest should return signatureInvalid",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{Features: featuremgmt.WithFeatures()},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "lacking-files")},
|
|
want: []*plugins.Plugin{},
|
|
pluginErrors: map[string]*plugins.SignatureError{
|
|
"test-datasource": {
|
|
PluginID: "test-datasource",
|
|
SignatureStatus: plugins.SignatureStatusInvalid,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Load a plugin with v1 manifest using PluginsAllowUnsigned config (production) should return signatureInvalid",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "lacking-files")},
|
|
want: []*plugins.Plugin{},
|
|
pluginErrors: map[string]*plugins.SignatureError{
|
|
"test-datasource": {
|
|
PluginID: "test-datasource",
|
|
SignatureStatus: plugins.SignatureStatusInvalid,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Load a plugin with manifest which has a file not found in plugin folder",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "invalid-v2-missing-file")},
|
|
want: []*plugins.Plugin{},
|
|
pluginErrors: map[string]*plugins.SignatureError{
|
|
"test-datasource": {
|
|
PluginID: "test-datasource",
|
|
SignatureStatus: plugins.SignatureStatusModified,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Load a plugin with file which is missing from the manifest",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{
|
|
PluginsAllowUnsigned: []string{"test-datasource"},
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "invalid-v2-extra-file")},
|
|
want: []*plugins.Plugin{},
|
|
pluginErrors: map[string]*plugins.SignatureError{
|
|
"test-datasource": {
|
|
PluginID: "test-datasource",
|
|
SignatureStatus: plugins.SignatureStatusModified,
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: "Load an app with includes",
|
|
class: plugins.ClassExternal,
|
|
cfg: &config.Cfg{
|
|
PluginsAllowUnsigned: []string{"test-app"},
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "test-app-with-includes")},
|
|
want: []*plugins.Plugin{
|
|
{JSONData: plugins.JSONData{
|
|
ID: "test-app",
|
|
Type: plugins.TypeApp,
|
|
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{
|
|
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: org.RoleViewer, Slug: "nginx-memory"},
|
|
{Name: "Root Page (react)", Type: "page", Role: org.RoleViewer, Path: "/a/my-simple-app", DefaultNav: true, AddToNav: true, Slug: "root-page-react"},
|
|
},
|
|
Backend: false,
|
|
},
|
|
DefaultNavURL: "/plugins/test-app/page/root-page-react",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "test-app-with-includes")),
|
|
Class: plugins.ClassExternal,
|
|
Signature: plugins.SignatureStatusUnsigned,
|
|
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",
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "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(testDataDir(t), "unsigned-datasource/plugin")),
|
|
Signature: plugins.SignatureStatusUnsigned,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
for _, tt := range tests {
|
|
reg := fakes.NewFakePluginRegistry()
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
errTracker := pluginerrs.ProvideSignatureErrorTracker()
|
|
l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, errTracker)
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := l.Load(context.Background(), sources.NewLocalSource(tt.class, tt.pluginPaths))
|
|
require.NoError(t, err)
|
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
|
}
|
|
|
|
pluginErrs := errTracker.SignatureErrors(context.Background())
|
|
require.Equal(t, len(tt.pluginErrors), len(pluginErrs))
|
|
for _, pluginErr := range pluginErrs {
|
|
require.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr)
|
|
}
|
|
|
|
verifyState(t, tt.want, reg, procPrvdr, procMgr)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoader_Load_ExternalRegistration(t *testing.T) {
|
|
boolPtr := func(b bool) *bool { return &b }
|
|
stringPtr := func(s string) *string { return &s }
|
|
|
|
t.Run("Load a plugin with oauth client registration", func(t *testing.T) {
|
|
cfg := &config.Cfg{
|
|
Features: fakes.NewFakeFeatureToggles(featuremgmt.FlagExternalServiceAuth),
|
|
PluginsAllowUnsigned: []string{"grafana-test-datasource"},
|
|
}
|
|
pluginPaths := []string{filepath.Join(testDataDir(t), "oauth-external-registration")}
|
|
expected := []*plugins.Plugin{
|
|
{JSONData: plugins.JSONData{
|
|
ID: "grafana-test-datasource",
|
|
Type: plugins.TypeDataSource,
|
|
Name: "Test",
|
|
Backend: true,
|
|
Executable: "gpx_test_datasource",
|
|
Info: plugins.Info{
|
|
Author: plugins.InfoLink{
|
|
Name: "Grafana Labs",
|
|
URL: "https://grafana.com",
|
|
},
|
|
Version: "1.0.0",
|
|
Logos: plugins.Logos{
|
|
Small: "/public/plugins/grafana-test-datasource/img/ds.svg",
|
|
Large: "/public/plugins/grafana-test-datasource/img/ds.svg",
|
|
},
|
|
Updated: "2023-08-03",
|
|
Screenshots: []plugins.Screenshots{},
|
|
},
|
|
Dependencies: plugins.Dependencies{
|
|
GrafanaVersion: "*",
|
|
Plugins: []plugins.Dependency{},
|
|
},
|
|
IAM: &plugindef.IAM{
|
|
Impersonation: &plugindef.Impersonation{
|
|
Groups: boolPtr(true),
|
|
Permissions: []plugindef.Permission{
|
|
{
|
|
Action: "read",
|
|
Scope: stringPtr("datasource"),
|
|
},
|
|
},
|
|
},
|
|
Permissions: []plugindef.Permission{
|
|
{
|
|
Action: "read",
|
|
Scope: stringPtr("datasource"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
FS: mustNewStaticFSForTests(t, pluginPaths[0]),
|
|
Class: plugins.ClassExternal,
|
|
Signature: plugins.SignatureStatusUnsigned,
|
|
Module: "/public/plugins/grafana-test-datasource/module.js",
|
|
BaseURL: "/public/plugins/grafana-test-datasource",
|
|
ExternalService: &auth.ExternalService{
|
|
ClientID: "client-id",
|
|
ClientSecret: "secretz",
|
|
PrivateKey: "priv@t3",
|
|
},
|
|
},
|
|
}
|
|
|
|
backendFactoryProvider := fakes.NewFakeBackendProcessProvider()
|
|
backendFactoryProvider.BackendFactoryFunc = func(ctx context.Context, plugin *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
|
return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) {
|
|
require.Equal(t, "grafana-test-datasource", pluginID)
|
|
require.Equal(t, []string{"GF_VERSION=", "GF_EDITION=", "GF_ENTERPRISE_LICENSE_PATH=",
|
|
"GF_ENTERPRISE_APP_URL=", "GF_ENTERPRISE_LICENSE_TEXT=", "GF_APP_URL=",
|
|
"GF_PLUGIN_APP_CLIENT_ID=client-id", "GF_PLUGIN_APP_CLIENT_SECRET=secretz",
|
|
"GF_PLUGIN_APP_PRIVATE_KEY=priv@t3", "GF_INSTANCE_FEATURE_TOGGLES_ENABLE=externalServiceAuth"}, env())
|
|
return &fakes.FakeBackendPlugin{}, nil
|
|
}
|
|
}
|
|
|
|
l := newLoaderWithOpts(t, cfg, loaderDepOpts{
|
|
authServiceRegistry: &fakes.FakeAuthService{
|
|
Result: &auth.ExternalService{
|
|
ClientID: "client-id",
|
|
ClientSecret: "secretz",
|
|
PrivateKey: "priv@t3",
|
|
},
|
|
},
|
|
backendFactoryProvider: backendFactoryProvider,
|
|
})
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return pluginPaths
|
|
},
|
|
DefaultSignatureFunc: func(ctx context.Context) (plugins.Signature, bool) {
|
|
return plugins.Signature{}, false
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
})
|
|
|
|
t.Run("Load a plugin with service account registration", func(t *testing.T) {
|
|
cfg := &config.Cfg{
|
|
Features: fakes.NewFakeFeatureToggles(featuremgmt.FlagExternalServiceAuth),
|
|
PluginsAllowUnsigned: []string{"grafana-test-datasource"},
|
|
}
|
|
pluginPaths := []string{filepath.Join(testDataDir(t), "external-registration")}
|
|
expected := []*plugins.Plugin{
|
|
{JSONData: plugins.JSONData{
|
|
ID: "grafana-test-datasource",
|
|
Type: plugins.TypeDataSource,
|
|
Name: "Test",
|
|
Backend: true,
|
|
Executable: "gpx_test_datasource",
|
|
Info: plugins.Info{
|
|
Author: plugins.InfoLink{
|
|
Name: "Grafana Labs",
|
|
URL: "https://grafana.com",
|
|
},
|
|
Version: "1.0.0",
|
|
Logos: plugins.Logos{
|
|
Small: "/public/plugins/grafana-test-datasource/img/ds.svg",
|
|
Large: "/public/plugins/grafana-test-datasource/img/ds.svg",
|
|
},
|
|
Updated: "2023-08-03",
|
|
Screenshots: []plugins.Screenshots{},
|
|
},
|
|
Dependencies: plugins.Dependencies{
|
|
GrafanaVersion: "*",
|
|
Plugins: []plugins.Dependency{},
|
|
},
|
|
IAM: &plugindef.IAM{
|
|
Permissions: []plugindef.Permission{
|
|
{
|
|
Action: "read",
|
|
Scope: stringPtr("datasource"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
FS: mustNewStaticFSForTests(t, pluginPaths[0]),
|
|
Class: plugins.ClassExternal,
|
|
Signature: plugins.SignatureStatusUnsigned,
|
|
Module: "/public/plugins/grafana-test-datasource/module.js",
|
|
BaseURL: "/public/plugins/grafana-test-datasource",
|
|
ExternalService: &auth.ExternalService{
|
|
ClientID: "client-id",
|
|
ClientSecret: "secretz",
|
|
},
|
|
},
|
|
}
|
|
|
|
backendFactoryProvider := fakes.NewFakeBackendProcessProvider()
|
|
backendFactoryProvider.BackendFactoryFunc = func(ctx context.Context, plugin *plugins.Plugin) backendplugin.PluginFactoryFunc {
|
|
return func(pluginID string, logger log.Logger, env func() []string) (backendplugin.Plugin, error) {
|
|
require.Equal(t, "grafana-test-datasource", pluginID)
|
|
require.Equal(t, []string{"GF_VERSION=", "GF_EDITION=", "GF_ENTERPRISE_LICENSE_PATH=",
|
|
"GF_ENTERPRISE_APP_URL=", "GF_ENTERPRISE_LICENSE_TEXT=", "GF_APP_URL=",
|
|
"GF_PLUGIN_APP_CLIENT_ID=client-id", "GF_PLUGIN_APP_CLIENT_SECRET=secretz",
|
|
"GF_INSTANCE_FEATURE_TOGGLES_ENABLE=externalServiceAuth"}, env())
|
|
return &fakes.FakeBackendPlugin{}, nil
|
|
}
|
|
}
|
|
|
|
l := newLoaderWithOpts(t, cfg, loaderDepOpts{
|
|
authServiceRegistry: &fakes.FakeAuthService{
|
|
Result: &auth.ExternalService{
|
|
ClientID: "client-id",
|
|
ClientSecret: "secretz",
|
|
},
|
|
},
|
|
backendFactoryProvider: backendFactoryProvider,
|
|
})
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return pluginPaths
|
|
},
|
|
DefaultSignatureFunc: func(ctx context.Context) (plugins.Signature, bool) {
|
|
return plugins.Signature{}, false
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLoader_Load_CustomSource(t *testing.T) {
|
|
t.Run("Load a plugin", func(t *testing.T) {
|
|
cfg := &config.Cfg{
|
|
PluginsCDNURLTemplate: "https://cdn.example.com",
|
|
PluginSettings: setting.PluginSettings{
|
|
"grafana-worldmap-panel": {"cdn": "true"},
|
|
},
|
|
Features: featuremgmt.WithFeatures(),
|
|
}
|
|
|
|
pluginPaths := []string{filepath.Join(testDataDir(t), "cdn")}
|
|
expected := []*plugins.Plugin{{
|
|
JSONData: plugins.JSONData{
|
|
ID: "grafana-worldmap-panel",
|
|
Type: plugins.TypePanel,
|
|
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{},
|
|
},
|
|
},
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "cdn/plugin")),
|
|
Class: plugins.ClassBundled,
|
|
Signature: plugins.SignatureStatusValid,
|
|
BaseURL: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel",
|
|
Module: "https://cdn.example.com/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel/module.js",
|
|
}}
|
|
|
|
l := newLoader(t, cfg, fakes.NewFakePluginRegistry(), fakes.NewFakeProcessManager(), fakes.NewFakeBackendProcessProvider(), newFakeSignatureErrorTracker())
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassBundled
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return pluginPaths
|
|
},
|
|
DefaultSignatureFunc: func(ctx context.Context) (plugins.Signature, bool) {
|
|
return plugins.Signature{
|
|
Status: plugins.SignatureStatusValid,
|
|
}, true
|
|
},
|
|
})
|
|
|
|
require.NoError(t, err)
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
|
t.Run("Load multiple", func(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cfg *config.Cfg
|
|
pluginPaths []string
|
|
existingPlugins map[string]struct{}
|
|
want []*plugins.Plugin
|
|
pluginErrors map[string]*plugins.SignatureError
|
|
}{
|
|
{
|
|
name: "Load multiple plugins (broken, valid, unsigned)",
|
|
cfg: &config.Cfg{
|
|
GrafanaAppURL: "http://localhost:3000",
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{
|
|
filepath.Join(testDataDir(t), "invalid-plugin-json"), // test-app
|
|
filepath.Join(testDataDir(t), "valid-v2-pvt-signature"), // test
|
|
filepath.Join(testDataDir(t), "unsigned-panel"), // test-panel
|
|
},
|
|
want: []*plugins.Plugin{
|
|
{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-datasource",
|
|
Type: plugins.TypeDataSource,
|
|
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.ReleaseStateAlpha,
|
|
},
|
|
Class: plugins.ClassExternal,
|
|
Module: "/public/plugins/test-datasource/module.js",
|
|
BaseURL: "/public/plugins/test-datasource",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "valid-v2-pvt-signature/plugin")),
|
|
Signature: "valid",
|
|
SignatureType: plugins.SignatureTypePrivate,
|
|
SignatureOrg: "Will Browne",
|
|
},
|
|
},
|
|
pluginErrors: map[string]*plugins.SignatureError{
|
|
"test-panel": {
|
|
PluginID: "test-panel",
|
|
SignatureStatus: plugins.SignatureStatusUnsigned,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
reg := fakes.NewFakePluginRegistry()
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
errTracker := pluginerrs.ProvideSignatureErrorTracker()
|
|
|
|
l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, errTracker)
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return tt.pluginPaths
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
return got[i].ID < got[j].ID
|
|
})
|
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
|
}
|
|
pluginErrs := errTracker.SignatureErrors(context.Background())
|
|
require.Equal(t, len(tt.pluginErrors), len(pluginErrs))
|
|
for _, pluginErr := range pluginErrs {
|
|
require.Equal(t, tt.pluginErrors[pluginErr.PluginID], pluginErr)
|
|
}
|
|
verifyState(t, tt.want, reg, procPrvdr, procMgr)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestLoader_Load_RBACReady(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
cfg *config.Cfg
|
|
pluginPaths []string
|
|
existingPlugins map[string]struct{}
|
|
want []*plugins.Plugin
|
|
}{
|
|
{
|
|
name: "Load plugin defining one RBAC role",
|
|
cfg: &config.Cfg{
|
|
GrafanaAppURL: "http://localhost:3000",
|
|
Features: featuremgmt.WithFeatures(),
|
|
},
|
|
pluginPaths: []string{filepath.Join(testDataDir(t), "test-app-with-roles")},
|
|
want: []*plugins.Plugin{
|
|
{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-app",
|
|
Type: plugins.TypeApp,
|
|
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{
|
|
Name: "Reader",
|
|
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,
|
|
},
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "test-app-with-roles")),
|
|
Class: plugins.ClassExternal,
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypePrivate,
|
|
SignatureOrg: "gabrielmabille",
|
|
Module: "/public/plugins/test-app/module.js",
|
|
BaseURL: "/public/plugins/test-app",
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
reg := fakes.NewFakePluginRegistry()
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
l := newLoader(t, tt.cfg, reg, procMgr, procPrvdr, newFakeSignatureErrorTracker())
|
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return tt.pluginPaths
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
if !cmp.Equal(got, tt.want, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
|
}
|
|
|
|
verifyState(t, tt.want, reg, procPrvdr, procMgr)
|
|
}
|
|
}
|
|
|
|
func TestLoader_Load_Signature_RootURL(t *testing.T) {
|
|
t.Run("Private signature verification ignores trailing slash in root URL", func(t *testing.T) {
|
|
const defaultAppURL = "http://localhost:3000/grafana"
|
|
|
|
expected := []*plugins.Plugin{
|
|
{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-datasource",
|
|
Type: plugins.TypeDataSource,
|
|
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.ReleaseStateAlpha,
|
|
Dependencies: plugins.Dependencies{GrafanaVersion: "*", Plugins: []plugins.Dependency{}},
|
|
Backend: true,
|
|
Executable: "test",
|
|
},
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "valid-v2-pvt-signature-root-url-uri/plugin")),
|
|
Class: plugins.ClassExternal,
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypePrivate,
|
|
SignatureOrg: "Will Browne",
|
|
Module: "/public/plugins/test-datasource/module.js",
|
|
BaseURL: "/public/plugins/test-datasource",
|
|
},
|
|
}
|
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
cfg := &config.Cfg{GrafanaAppURL: defaultAppURL, Features: featuremgmt.WithFeatures()}
|
|
l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeSignatureErrorTracker())
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "valid-v2-pvt-signature-root-url-uri")}
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
|
})
|
|
}
|
|
|
|
func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
|
t.Run("Load duplicate plugin folders", func(t *testing.T) {
|
|
expected := []*plugins.Plugin{
|
|
{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-app",
|
|
Type: plugins.TypeApp,
|
|
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: org.RoleViewer, Slug: "nginx-connections"},
|
|
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: org.RoleViewer, Slug: "nginx-memory"},
|
|
{Name: "Nginx Panel", Type: "panel", Role: org.RoleViewer, Slug: "nginx-panel"},
|
|
{Name: "Nginx Datasource", Type: "datasource", Role: org.RoleViewer, Slug: "nginx-datasource"},
|
|
},
|
|
Backend: false,
|
|
},
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "test-app")),
|
|
Class: plugins.ClassExternal,
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
Module: "/public/plugins/test-app/module.js",
|
|
BaseURL: "/public/plugins/test-app",
|
|
},
|
|
}
|
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
cfg := &config.Cfg{Features: featuremgmt.WithFeatures()}
|
|
l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeSignatureErrorTracker())
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "test-app"), filepath.Join(testDataDir(t), "test-app")}
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
|
})
|
|
}
|
|
|
|
func TestLoader_Load_SkipUninitializedPlugins(t *testing.T) {
|
|
t.Run("Load duplicate plugin folders", func(t *testing.T) {
|
|
pluginDir1 := filepath.Join(testDataDir(t), "test-app")
|
|
pluginDir2 := filepath.Join(testDataDir(t), "valid-v2-signature")
|
|
|
|
expected := []*plugins.Plugin{
|
|
{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-app",
|
|
Type: plugins.TypeApp,
|
|
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: org.RoleViewer, Slug: "nginx-connections"},
|
|
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: org.RoleViewer, Slug: "nginx-memory"},
|
|
{Name: "Nginx Panel", Type: "panel", Role: org.RoleViewer, Slug: "nginx-panel"},
|
|
{Name: "Nginx Datasource", Type: "datasource", Role: org.RoleViewer, Slug: "nginx-datasource"},
|
|
},
|
|
Backend: false,
|
|
},
|
|
FS: mustNewStaticFSForTests(t, pluginDir1),
|
|
Class: plugins.ClassExternal,
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
Module: "/public/plugins/test-app/module.js",
|
|
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, _ func() []string) (backendplugin.Plugin, error) {
|
|
if pluginID == "test-datasource" {
|
|
return nil, errors.New("failed to initialize")
|
|
}
|
|
return &fakes.FakePluginClient{}, nil
|
|
}
|
|
}
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
cfg := &config.Cfg{Features: featuremgmt.WithFeatures()}
|
|
l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeSignatureErrorTracker())
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
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...))
|
|
}
|
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
|
})
|
|
}
|
|
|
|
func TestLoader_AngularClass(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
class plugins.Class
|
|
expAngularDetectionRun bool
|
|
}{
|
|
{
|
|
name: "core plugin should skip angular detection",
|
|
class: plugins.ClassCore,
|
|
expAngularDetectionRun: false,
|
|
},
|
|
{
|
|
name: "bundled plugin should skip angular detection",
|
|
class: plugins.ClassBundled,
|
|
expAngularDetectionRun: false,
|
|
},
|
|
{
|
|
name: "external plugin should run angular detection",
|
|
class: plugins.ClassExternal,
|
|
expAngularDetectionRun: true,
|
|
},
|
|
{
|
|
name: "other-class plugin should run angular detection",
|
|
class: "CDN", // (enterprise-only class)
|
|
expAngularDetectionRun: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
fakePluginSource := &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return tc.class
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "valid-v2-signature")}
|
|
},
|
|
}
|
|
// if angularDetected = true, it means that the detection has run
|
|
l := newLoaderWithOpts(t, &config.Cfg{AngularSupportEnabled: true, Features: featuremgmt.WithFeatures()}, loaderDepOpts{
|
|
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
|
})
|
|
p, err := l.Load(context.Background(), fakePluginSource)
|
|
require.NoError(t, err)
|
|
require.Len(t, p, 1, "should load 1 plugin")
|
|
if tc.expAngularDetectionRun {
|
|
require.True(t, p[0].Angular.Detected, "angular detection should run")
|
|
} else {
|
|
require.False(t, p[0].Angular.Detected, "angular detection should not run")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoader_Load_Angular(t *testing.T) {
|
|
fakePluginSource := &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "valid-v2-signature")}
|
|
},
|
|
}
|
|
for _, cfgTc := range []struct {
|
|
name string
|
|
cfg *config.Cfg
|
|
}{
|
|
{name: "angular support enabled", cfg: &config.Cfg{AngularSupportEnabled: true, Features: featuremgmt.WithFeatures()}},
|
|
{name: "angular support disabled", cfg: &config.Cfg{AngularSupportEnabled: false, Features: featuremgmt.WithFeatures()}},
|
|
} {
|
|
t.Run(cfgTc.name, func(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
angularInspector angularinspector.Inspector
|
|
shouldLoad bool
|
|
}{
|
|
{
|
|
name: "angular plugin",
|
|
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
|
// angular plugins should load only if allowed by the cfg
|
|
shouldLoad: cfgTc.cfg.AngularSupportEnabled,
|
|
},
|
|
{
|
|
name: "non angular plugin",
|
|
angularInspector: angularinspector.NeverAngularFakeInspector,
|
|
// non-angular plugins should always load
|
|
shouldLoad: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
l := newLoaderWithOpts(t, cfgTc.cfg, loaderDepOpts{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")
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoader_HideAngularDeprecation(t *testing.T) {
|
|
fakePluginSource := &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "valid-v2-signature")}
|
|
},
|
|
}
|
|
for _, tc := range []struct {
|
|
name string
|
|
cfg *config.Cfg
|
|
expHideAngularDeprecation bool
|
|
}{
|
|
{name: "with plugin id in HideAngularDeprecation list", cfg: &config.Cfg{
|
|
AngularSupportEnabled: true,
|
|
HideAngularDeprecation: []string{"one-app", "two-panel", "test-datasource", "three-datasource"},
|
|
Features: featuremgmt.WithFeatures(),
|
|
}, expHideAngularDeprecation: true},
|
|
{name: "without plugin id in HideAngularDeprecation list", cfg: &config.Cfg{
|
|
AngularSupportEnabled: true,
|
|
HideAngularDeprecation: []string{"one-app", "two-panel", "three-datasource"},
|
|
Features: featuremgmt.WithFeatures(),
|
|
}, expHideAngularDeprecation: false},
|
|
{name: "with empty HideAngularDeprecation", cfg: &config.Cfg{
|
|
AngularSupportEnabled: true,
|
|
HideAngularDeprecation: nil,
|
|
Features: featuremgmt.WithFeatures(),
|
|
}, expHideAngularDeprecation: false},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
l := newLoaderWithOpts(t, tc.cfg, loaderDepOpts{
|
|
angularInspector: angularinspector.AlwaysAngularFakeInspector,
|
|
})
|
|
p, err := l.Load(context.Background(), fakePluginSource)
|
|
require.NoError(t, err)
|
|
require.Len(t, p, 1, "should load 1 plugin")
|
|
require.Equal(t, tc.expHideAngularDeprecation, p[0].Angular.HideDeprecation)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestLoader_Load_NestedPlugins(t *testing.T) {
|
|
parent := &plugins.Plugin{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-datasource",
|
|
Type: plugins.TypeDataSource,
|
|
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,
|
|
},
|
|
Module: "/public/plugins/test-datasource/module.js",
|
|
BaseURL: "/public/plugins/test-datasource",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "nested-plugins/parent")),
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
Class: plugins.ClassExternal,
|
|
}
|
|
|
|
child := &plugins.Plugin{
|
|
JSONData: plugins.JSONData{
|
|
ID: "test-panel",
|
|
Type: plugins.TypePanel,
|
|
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{},
|
|
},
|
|
},
|
|
Module: "/public/plugins/test-panel/module.js",
|
|
BaseURL: "/public/plugins/test-panel",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "nested-plugins/parent/nested")),
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
Class: plugins.ClassExternal,
|
|
}
|
|
|
|
parent.Children = []*plugins.Plugin{child}
|
|
child.Parent = parent
|
|
|
|
t.Run("Load nested External plugins", func(t *testing.T) {
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
reg := fakes.NewFakePluginRegistry()
|
|
cfg := &config.Cfg{Features: featuremgmt.WithFeatures()}
|
|
l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeSignatureErrorTracker())
|
|
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "nested-plugins")}
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// to ensure we can compare with expected
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
return got[i].ID < got[j].ID
|
|
})
|
|
|
|
expected := []*plugins.Plugin{parent, child}
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
|
|
|
t.Run("Load will exclude plugins that already exist", func(t *testing.T) {
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "nested-plugins")}
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// to ensure we can compare with expected
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
return got[i].ID < got[j].ID
|
|
})
|
|
|
|
if !cmp.Equal(got, []*plugins.Plugin{}, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
|
})
|
|
})
|
|
|
|
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: plugins.TypeApp,
|
|
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: "",
|
|
Updated: "",
|
|
},
|
|
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: org.RoleViewer,
|
|
AddToNav: true,
|
|
DefaultNav: true,
|
|
Slug: "root-page-react",
|
|
},
|
|
{
|
|
Name: "Root Page (Tab B)",
|
|
Path: "/a/myorgid-simple-app/?tab=b",
|
|
Type: "page",
|
|
Role: org.RoleViewer,
|
|
AddToNav: true,
|
|
Slug: "root-page-tab-b",
|
|
},
|
|
{
|
|
Name: "React Config",
|
|
Path: "/plugins/myorgid-simple-app/?page=page2",
|
|
Type: "page",
|
|
Role: org.RoleAdmin,
|
|
AddToNav: true,
|
|
Slug: "react-config",
|
|
},
|
|
{
|
|
Name: "Streaming Example",
|
|
Path: "dashboards/streaming.json",
|
|
Type: "dashboard",
|
|
Role: org.RoleViewer,
|
|
Slug: "streaming-example",
|
|
},
|
|
{
|
|
Name: "Lots of Stats",
|
|
Path: "dashboards/stats.json",
|
|
Type: "dashboard",
|
|
Role: org.RoleViewer,
|
|
Slug: "lots-of-stats",
|
|
},
|
|
},
|
|
Backend: false,
|
|
},
|
|
Module: "/public/plugins/myorgid-simple-app/module.js",
|
|
BaseURL: "/public/plugins/myorgid-simple-app",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "app-with-child/dist")),
|
|
DefaultNavURL: "/plugins/myorgid-simple-app/page/root-page-react",
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
Class: plugins.ClassExternal,
|
|
}
|
|
|
|
child := &plugins.Plugin{
|
|
JSONData: plugins.JSONData{
|
|
ID: "myorgid-simple-panel",
|
|
Type: plugins.TypePanel,
|
|
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: "",
|
|
Updated: "",
|
|
},
|
|
Dependencies: plugins.Dependencies{
|
|
GrafanaDependency: ">=7.0.0",
|
|
GrafanaVersion: "*",
|
|
Plugins: []plugins.Dependency{},
|
|
},
|
|
},
|
|
Module: "/public/plugins/myorgid-simple-app/child/module.js",
|
|
BaseURL: "/public/plugins/myorgid-simple-app",
|
|
FS: mustNewStaticFSForTests(t, filepath.Join(testDataDir(t), "app-with-child/dist/child")),
|
|
IncludedInAppID: parent.ID,
|
|
Signature: plugins.SignatureStatusValid,
|
|
SignatureType: plugins.SignatureTypeGrafana,
|
|
SignatureOrg: "Grafana Labs",
|
|
Class: plugins.ClassExternal,
|
|
}
|
|
|
|
parent.Children = []*plugins.Plugin{child}
|
|
child.Parent = parent
|
|
expected := []*plugins.Plugin{parent, child}
|
|
|
|
reg := fakes.NewFakePluginRegistry()
|
|
procPrvdr := fakes.NewFakeBackendProcessProvider()
|
|
procMgr := fakes.NewFakeProcessManager()
|
|
cfg := &config.Cfg{Features: featuremgmt.WithFeatures()}
|
|
l := newLoader(t, cfg, reg, procMgr, procPrvdr, newFakeSignatureErrorTracker())
|
|
got, err := l.Load(context.Background(), &fakes.FakePluginSource{
|
|
PluginClassFunc: func(ctx context.Context) plugins.Class {
|
|
return plugins.ClassExternal
|
|
},
|
|
PluginURIsFunc: func(ctx context.Context) []string {
|
|
return []string{filepath.Join(testDataDir(t), "app-with-child")}
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
// to ensure we can compare with expected
|
|
sort.SliceStable(got, func(i, j int) bool {
|
|
return got[i].ID < got[j].ID
|
|
})
|
|
|
|
if !cmp.Equal(got, expected, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
|
}
|
|
|
|
verifyState(t, expected, reg, procPrvdr, procMgr)
|
|
})
|
|
}
|
|
|
|
type loaderDepOpts struct {
|
|
angularInspector angularinspector.Inspector
|
|
authServiceRegistry auth.ExternalServiceRegistry
|
|
backendFactoryProvider plugins.BackendFactoryProvider
|
|
}
|
|
|
|
func newLoader(t *testing.T, cfg *config.Cfg, reg registry.Service, proc process.Manager,
|
|
backendFactory plugins.BackendFactoryProvider, sigErrTracker pluginerrs.SignatureErrorTracker) *Loader {
|
|
assets := assetpath.ProvideService(cfg, pluginscdn.ProvideService(cfg))
|
|
lic := fakes.NewFakeLicensingService()
|
|
angularInspector := angularinspector.NewStaticInspector()
|
|
|
|
terminate, err := pipeline.ProvideTerminationStage(cfg, reg, proc)
|
|
require.NoError(t, err)
|
|
|
|
return ProvideService(pipeline.ProvideDiscoveryStage(cfg,
|
|
finder.NewLocalFinder(false, featuremgmt.WithFeatures()), reg),
|
|
pipeline.ProvideBootstrapStage(cfg, signature.DefaultCalculator(cfg), assets),
|
|
pipeline.ProvideValidationStage(cfg, signature.NewValidator(signature.NewUnsignedAuthorizer(cfg)), angularInspector, sigErrTracker),
|
|
pipeline.ProvideInitializationStage(cfg, reg, lic, backendFactory, proc, &fakes.FakeAuthService{}, fakes.NewFakeRoleRegistry()),
|
|
terminate)
|
|
}
|
|
|
|
func newLoaderWithOpts(t *testing.T, cfg *config.Cfg, opts loaderDepOpts) *Loader {
|
|
assets := assetpath.ProvideService(cfg, pluginscdn.ProvideService(cfg))
|
|
lic := fakes.NewFakeLicensingService()
|
|
reg := fakes.NewFakePluginRegistry()
|
|
proc := fakes.NewFakeProcessManager()
|
|
|
|
terminate, err := pipeline.ProvideTerminationStage(cfg, reg, proc)
|
|
require.NoError(t, err)
|
|
sigErrTracker := pluginerrs.ProvideSignatureErrorTracker()
|
|
|
|
angularInspector := opts.angularInspector
|
|
if opts.angularInspector == nil {
|
|
angularInspector = angularinspector.NewStaticInspector()
|
|
}
|
|
|
|
authServiceRegistry := opts.authServiceRegistry
|
|
if authServiceRegistry == nil {
|
|
authServiceRegistry = &fakes.FakeAuthService{}
|
|
}
|
|
|
|
backendFactoryProvider := opts.backendFactoryProvider
|
|
if backendFactoryProvider == nil {
|
|
backendFactoryProvider = fakes.NewFakeBackendProcessProvider()
|
|
}
|
|
|
|
return ProvideService(pipeline.ProvideDiscoveryStage(cfg,
|
|
finder.NewLocalFinder(false, featuremgmt.WithFeatures()), reg),
|
|
pipeline.ProvideBootstrapStage(cfg, signature.DefaultCalculator(cfg), assets),
|
|
pipeline.ProvideValidationStage(cfg, signature.NewValidator(signature.NewUnsignedAuthorizer(cfg)), angularInspector, sigErrTracker),
|
|
pipeline.ProvideInitializationStage(cfg, reg, lic, backendFactoryProvider, proc, authServiceRegistry, fakes.NewFakeRoleRegistry()),
|
|
terminate)
|
|
}
|
|
|
|
func verifyState(t *testing.T, ps []*plugins.Plugin, reg registry.Service,
|
|
procPrvdr *fakes.FakeBackendProcessProvider, procMngr *fakes.FakeProcessManager) {
|
|
t.Helper()
|
|
|
|
for _, p := range ps {
|
|
regP, exists := reg.Plugin(context.Background(), p.ID)
|
|
require.True(t, exists)
|
|
if !cmp.Equal(p, regP, compareOpts...) {
|
|
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(p, regP, compareOpts...))
|
|
}
|
|
|
|
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])
|
|
}
|
|
|
|
require.Equal(t, 1, procMngr.Started[p.ID])
|
|
require.Zero(t, procMngr.Stopped[p.ID])
|
|
}
|
|
}
|
|
|
|
func mustNewStaticFSForTests(t *testing.T, dir string) plugins.FS {
|
|
sfs, err := plugins.NewStaticFS(plugins.NewLocalFS(dir))
|
|
require.NoError(t, err)
|
|
return sfs
|
|
}
|
|
|
|
func testDataDir(t *testing.T) string {
|
|
dir, err := filepath.Abs("../../../plugins/manager/testdata")
|
|
if err != nil {
|
|
t.Errorf("could not construct absolute path of current dir")
|
|
return ""
|
|
}
|
|
return dir
|
|
}
|
|
|
|
func corePluginDir(t *testing.T) string {
|
|
dir, err := filepath.Abs("./../../../../public")
|
|
if err != nil {
|
|
t.Errorf("could not construct absolute path of core plugins dir")
|
|
return ""
|
|
}
|
|
return dir
|
|
}
|