mirror of
https://github.com/grafana/grafana.git
synced 2025-08-01 08:52:38 +08:00
Plugins: Add Plugin FS abstraction (#63734)
* unexport pluginDir from dto * first pass * tidy * naming + add mutex * add dupe checking * fix func typo * interface + move logic from renderer * remote finder * remote signing * fix tests * tidy up * tidy markdown logic * split changes * fix tests * slim interface down * fix status code * tidy exec path func * fixup * undo changes * remove unused func * remove unused func * fix goimports * fetch remotely * simultaneous support * fix linter * use var * add exception for gosec warning * fixup * fix tests * tidy * rework cfg pattern * simplify * PR feedback * fix dupe field * remove g304 nolint * apply PR feedback * remove unnecessary gosec nolint * fix finder loop and update comment * fix map alloc * fix test * remove commented code
This commit is contained in:
@ -2,7 +2,7 @@ package loader
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"testing"
|
||||
@ -20,11 +20,25 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/fakes"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/loader/initializer"
|
||||
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var compareOpts = cmpopts.IgnoreFields(plugins.Plugin{}, "client", "log")
|
||||
var compareOpts = []cmp.Option{cmpopts.IgnoreFields(plugins.Plugin{}, "client", "log"), localFSComparer}
|
||||
|
||||
var localFSComparer = cmp.Comparer(func(fs1 plugins.LocalFS, fs2 plugins.LocalFS) bool {
|
||||
fs1Files := fs1.Files()
|
||||
fs2Files := fs2.Files()
|
||||
|
||||
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) {
|
||||
corePluginDir, err := filepath.Abs("./../../../../public")
|
||||
@ -86,9 +100,11 @@ func TestLoader_Load(t *testing.T) {
|
||||
Backend: true,
|
||||
QueryOptions: map[string]bool{"minInterval": true},
|
||||
},
|
||||
Module: "app/plugins/datasource/cloudwatch/module",
|
||||
BaseURL: "public/app/plugins/datasource/cloudwatch",
|
||||
PluginDir: filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch"),
|
||||
Module: "app/plugins/datasource/cloudwatch/module",
|
||||
BaseURL: "public/app/plugins/datasource/cloudwatch",
|
||||
FS: plugins.NewLocalFS(
|
||||
filesInDir(t, filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch")),
|
||||
filepath.Join(corePluginDir, "app/plugins/datasource/cloudwatch")),
|
||||
Signature: plugins.SignatureInternal,
|
||||
Class: plugins.Core,
|
||||
},
|
||||
@ -125,9 +141,12 @@ func TestLoader_Load(t *testing.T) {
|
||||
Backend: true,
|
||||
State: "alpha",
|
||||
},
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/valid-v2-signature/plugin/"),
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
FS: plugins.NewLocalFS(
|
||||
filesInDir(t, filepath.Join(parentDir, "testdata/valid-v2-signature/plugin/")),
|
||||
filepath.Join(parentDir, "testdata/valid-v2-signature/plugin/"),
|
||||
),
|
||||
Signature: "valid",
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
@ -201,10 +220,20 @@ func TestLoader_Load(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-app/module",
|
||||
BaseURL: "public/plugins/test-app",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/includes-symlinks"),
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-app/module",
|
||||
BaseURL: "public/plugins/test-app",
|
||||
FS: plugins.NewLocalFS(
|
||||
map[string]struct{}{
|
||||
filepath.Join(parentDir, "testdata/includes-symlinks", "/MANIFEST.txt"): {},
|
||||
filepath.Join(parentDir, "testdata/includes-symlinks", "dashboards/connections.json"): {},
|
||||
filepath.Join(parentDir, "testdata/includes-symlinks", "dashboards/extra/memory.json"): {},
|
||||
filepath.Join(parentDir, "testdata/includes-symlinks", "plugin.json"): {},
|
||||
filepath.Join(parentDir, "testdata/includes-symlinks", "symlink_to_txt"): {},
|
||||
filepath.Join(parentDir, "testdata/includes-symlinks", "text.txt"): {},
|
||||
},
|
||||
filepath.Join(parentDir, "testdata/includes-symlinks"),
|
||||
),
|
||||
Signature: "valid",
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
@ -241,10 +270,13 @@ func TestLoader_Load(t *testing.T) {
|
||||
Backend: true,
|
||||
State: plugins.AlphaRelease,
|
||||
},
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/unsigned-datasource/plugin"),
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
FS: plugins.NewLocalFS(
|
||||
filesInDir(t, filepath.Join(parentDir, "testdata/unsigned-datasource/plugin")),
|
||||
filepath.Join(parentDir, "testdata/unsigned-datasource/plugin"),
|
||||
),
|
||||
Signature: "unsigned",
|
||||
},
|
||||
},
|
||||
@ -292,10 +324,13 @@ func TestLoader_Load(t *testing.T) {
|
||||
Backend: true,
|
||||
State: plugins.AlphaRelease,
|
||||
},
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/unsigned-datasource/plugin"),
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
FS: plugins.NewLocalFS(
|
||||
filesInDir(t, filepath.Join(parentDir, "testdata/unsigned-datasource/plugin")),
|
||||
filepath.Join(parentDir, "testdata/unsigned-datasource/plugin"),
|
||||
),
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
},
|
||||
},
|
||||
@ -399,11 +434,14 @@ func TestLoader_Load(t *testing.T) {
|
||||
Backend: false,
|
||||
},
|
||||
DefaultNavURL: "/plugins/test-app/page/root-page-react",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/test-app-with-includes"),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
Module: "plugins/test-app/module",
|
||||
BaseURL: "public/plugins/test-app",
|
||||
FS: plugins.NewLocalFS(map[string]struct{}{
|
||||
filepath.Join(parentDir, "testdata/test-app-with-includes", "dashboards/memory.json"): {},
|
||||
filepath.Join(parentDir, "testdata/test-app-with-includes", "plugin.json"): {},
|
||||
}, filepath.Join(parentDir, "testdata/test-app-with-includes")),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureUnsigned,
|
||||
Module: "plugins/test-app/module",
|
||||
BaseURL: "public/plugins/test-app",
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -454,7 +492,9 @@ func TestLoader_Load(t *testing.T) {
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
},
|
||||
PluginDir: filepath.Join(parentDir, "testdata/cdn/plugin"),
|
||||
FS: plugins.NewLocalFS(map[string]struct{}{
|
||||
filepath.Join(parentDir, "testdata/cdn/plugin", "plugin.json"): {},
|
||||
}, filepath.Join(parentDir, "testdata/cdn/plugin")),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureValid,
|
||||
BaseURL: "plugin-cdn/grafana-worldmap-panel/0.3.3/public/plugins/grafana-worldmap-panel",
|
||||
@ -478,8 +518,8 @@ func TestLoader_Load(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), 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))
|
||||
if !cmp.Equal(got, tt.want, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
||||
}
|
||||
|
||||
pluginErrs := l.PluginErrors()
|
||||
@ -600,10 +640,13 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
Executable: "test",
|
||||
State: plugins.AlphaRelease,
|
||||
},
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
PluginDir: filepath.Join(parentDir, "testdata/valid-v2-pvt-signature/plugin"),
|
||||
Class: plugins.External,
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
FS: plugins.NewLocalFS(map[string]struct{}{
|
||||
filepath.Join(parentDir, "testdata/valid-v2-pvt-signature/plugin/plugin.json"): {},
|
||||
filepath.Join(parentDir, "testdata/valid-v2-pvt-signature/plugin/MANIFEST.txt"): {},
|
||||
}, filepath.Join(parentDir, "testdata/valid-v2-pvt-signature/plugin")),
|
||||
Signature: "valid",
|
||||
SignatureType: plugins.PrivateSignature,
|
||||
SignatureOrg: "Will Browne",
|
||||
@ -641,8 +684,8 @@ func TestLoader_Load_MultiplePlugins(t *testing.T) {
|
||||
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))
|
||||
if !cmp.Equal(got, tt.want, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
||||
}
|
||||
pluginErrs := l.PluginErrors()
|
||||
require.Equal(t, len(tt.pluginErrors), len(pluginErrs))
|
||||
@ -717,7 +760,10 @@ func TestLoader_Load_RBACReady(t *testing.T) {
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
PluginDir: pluginDir,
|
||||
FS: plugins.NewLocalFS(map[string]struct{}{
|
||||
filepath.Join(pluginDir, "plugin.json"): {},
|
||||
filepath.Join(pluginDir, "MANIFEST.txt"): {},
|
||||
}, pluginDir),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.PrivateSignature,
|
||||
@ -749,8 +795,8 @@ func TestLoader_Load_RBACReady(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), plugins.External, 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))
|
||||
if !cmp.Equal(got, tt.want, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, tt.want, compareOpts...))
|
||||
}
|
||||
pluginErrs := l.PluginErrors()
|
||||
require.Len(t, pluginErrs, 0)
|
||||
@ -799,7 +845,10 @@ func TestLoader_Load_Signature_RootURL(t *testing.T) {
|
||||
Backend: true,
|
||||
Executable: "test",
|
||||
},
|
||||
PluginDir: filepath.Join(parentDir, "/testdata/valid-v2-pvt-signature-root-url-uri/plugin"),
|
||||
FS: plugins.NewLocalFS(map[string]struct{}{
|
||||
filepath.Join(filepath.Join(parentDir, "/testdata/valid-v2-pvt-signature-root-url-uri/plugin"), "plugin.json"): {},
|
||||
filepath.Join(filepath.Join(parentDir, "/testdata/valid-v2-pvt-signature-root-url-uri/plugin"), "MANIFEST.txt"): {},
|
||||
}, filepath.Join(parentDir, "/testdata/valid-v2-pvt-signature-root-url-uri/plugin")),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.PrivateSignature,
|
||||
@ -822,8 +871,8 @@ func TestLoader_Load_Signature_RootURL(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), plugins.External, paths)
|
||||
require.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
if !cmp.Equal(got, expected, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
||||
}
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
})
|
||||
@ -878,7 +927,7 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
PluginDir: pluginDir,
|
||||
FS: plugins.NewLocalFS(filesInDir(t, pluginDir), pluginDir),
|
||||
Class: plugins.External,
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
@ -901,8 +950,8 @@ func TestLoader_Load_DuplicatePlugins(t *testing.T) {
|
||||
got, err := l.Load(context.Background(), plugins.External, []string{pluginDir, pluginDir})
|
||||
require.NoError(t, err)
|
||||
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
if !cmp.Equal(got, expected, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
||||
}
|
||||
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
@ -939,9 +988,10 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
},
|
||||
Backend: true,
|
||||
},
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
PluginDir: filepath.Join(rootDir, "testdata/nested-plugins/parent"),
|
||||
Module: "plugins/test-datasource/module",
|
||||
BaseURL: "public/plugins/test-datasource",
|
||||
FS: plugins.NewLocalFS(filesInDir(t, filepath.Join(rootDir, "testdata/nested-plugins/parent")),
|
||||
filepath.Join(rootDir, "testdata/nested-plugins/parent")),
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
@ -971,9 +1021,10 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
},
|
||||
Module: "plugins/test-panel/module",
|
||||
BaseURL: "public/plugins/test-panel",
|
||||
PluginDir: filepath.Join(rootDir, "testdata/nested-plugins/parent/nested"),
|
||||
Module: "plugins/test-panel/module",
|
||||
BaseURL: "public/plugins/test-panel",
|
||||
FS: plugins.NewLocalFS(filesInDir(t, filepath.Join(rootDir, "testdata/nested-plugins/parent/nested")),
|
||||
filepath.Join(rootDir, "testdata/nested-plugins/parent/nested")),
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
SignatureOrg: "Grafana Labs",
|
||||
@ -1004,8 +1055,8 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
})
|
||||
|
||||
expected := []*plugins.Plugin{parent, child}
|
||||
if !cmp.Equal(got, expected, compareOpts) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts))
|
||||
if !cmp.Equal(got, expected, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
||||
}
|
||||
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
@ -1019,8 +1070,8 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
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))
|
||||
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, storage, procMgr)
|
||||
@ -1098,9 +1149,10 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
Module: "plugins/myorgid-simple-app/module",
|
||||
BaseURL: "public/plugins/myorgid-simple-app",
|
||||
PluginDir: filepath.Join(rootDir, "testdata/app-with-child/dist"),
|
||||
Module: "plugins/myorgid-simple-app/module",
|
||||
BaseURL: "public/plugins/myorgid-simple-app",
|
||||
FS: plugins.NewLocalFS(filesInDir(t, filepath.Join(rootDir, "testdata/app-with-child/dist")),
|
||||
filepath.Join(rootDir, "testdata/app-with-child/dist")),
|
||||
DefaultNavURL: "/plugins/myorgid-simple-app/page/root-page-react",
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
@ -1136,9 +1188,10 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
Plugins: []plugins.Dependency{},
|
||||
},
|
||||
},
|
||||
Module: "plugins/myorgid-simple-app/child/module",
|
||||
BaseURL: "public/plugins/myorgid-simple-app",
|
||||
PluginDir: filepath.Join(rootDir, "testdata/app-with-child/dist/child"),
|
||||
Module: "plugins/myorgid-simple-app/child/module",
|
||||
BaseURL: "public/plugins/myorgid-simple-app",
|
||||
FS: plugins.NewLocalFS(filesInDir(t, filepath.Join(rootDir, "testdata/app-with-child/dist/child")),
|
||||
filepath.Join(rootDir, "testdata/app-with-child/dist/child")),
|
||||
IncludedInAppID: parent.ID,
|
||||
Signature: plugins.SignatureValid,
|
||||
SignatureType: plugins.GrafanaSignature,
|
||||
@ -1168,205 +1221,27 @@ func TestLoader_Load_NestedPlugins(t *testing.T) {
|
||||
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))
|
||||
if !cmp.Equal(got, expected, compareOpts...) {
|
||||
t.Fatalf("Result mismatch (-want +got):\n%s", cmp.Diff(got, expected, compareOpts...))
|
||||
}
|
||||
|
||||
verifyState(t, expected, reg, procPrvdr, storage, procMgr)
|
||||
|
||||
t.Run("order of loaded parent and child plugins gives same output", func(t *testing.T) {
|
||||
parentPluginJSON := filepath.Join(rootDir, "testdata/app-with-child/dist/plugin.json")
|
||||
childPluginJSON := filepath.Join(rootDir, "testdata/app-with-child/dist/child/plugin.json")
|
||||
|
||||
reg = fakes.NewFakePluginRegistry()
|
||||
storage = fakes.NewFakePluginStorage()
|
||||
procPrvdr = fakes.NewFakeBackendProcessProvider()
|
||||
procMgr = fakes.NewFakeProcessManager()
|
||||
l = newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err = l.loadPlugins(context.Background(), plugins.External, []string{parentPluginJSON, childPluginJSON})
|
||||
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, storage, procMgr)
|
||||
|
||||
reg = fakes.NewFakePluginRegistry()
|
||||
storage = fakes.NewFakePluginStorage()
|
||||
procPrvdr = fakes.NewFakeBackendProcessProvider()
|
||||
procMgr = fakes.NewFakeProcessManager()
|
||||
l = newLoader(&config.Cfg{}, func(l *Loader) {
|
||||
l.pluginRegistry = reg
|
||||
l.pluginStorage = storage
|
||||
l.processManager = procMgr
|
||||
l.pluginInitializer = initializer.New(&config.Cfg{}, procPrvdr, fakes.NewFakeLicensingService())
|
||||
})
|
||||
got, err = l.loadPlugins(context.Background(), plugins.External, []string{childPluginJSON, parentPluginJSON})
|
||||
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, storage, procMgr)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func TestLoader_readPluginJSON(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pluginPath string
|
||||
expected plugins.JSONData
|
||||
failed bool
|
||||
}{
|
||||
{
|
||||
name: "Valid plugin",
|
||||
pluginPath: "../testdata/test-app/plugin.json",
|
||||
expected: 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: "img/logo_small.png",
|
||||
Large: "img/logo_large.png",
|
||||
},
|
||||
Screenshots: []plugins.Screenshots{
|
||||
{Path: "img/screenshot1.png", Name: "img1"},
|
||||
{Path: "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},
|
||||
{Name: "Nginx Memory", Path: "dashboards/memory.json", Type: "dashboard", Role: org.RoleViewer},
|
||||
{Name: "Nginx Panel", Type: "panel", Role: org.RoleViewer},
|
||||
{Name: "Nginx Datasource", Type: "datasource", Role: org.RoleViewer},
|
||||
},
|
||||
Backend: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid plugin JSON",
|
||||
pluginPath: "../testdata/invalid-plugin-json/plugin.json",
|
||||
failed: true,
|
||||
},
|
||||
{
|
||||
name: "Non-existing JSON file",
|
||||
pluginPath: "nonExistingFile.json",
|
||||
failed: true,
|
||||
},
|
||||
}
|
||||
|
||||
l := newLoader(nil)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := l.readPluginJSON(tt.pluginPath)
|
||||
if (err != nil) && !tt.failed {
|
||||
t.Errorf("readPluginJSON() error = %v, failed %v", err, tt.failed)
|
||||
return
|
||||
}
|
||||
if !cmp.Equal(got, tt.expected, compareOpts) {
|
||||
t.Errorf("Unexpected pluginJSONData: %v", cmp.Diff(got, tt.expected, compareOpts))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_validatePluginJSON(t *testing.T) {
|
||||
type args struct {
|
||||
data plugins.JSONData
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "Valid case",
|
||||
args: args{
|
||||
data: plugins.JSONData{
|
||||
ID: "grafana-plugin-id",
|
||||
Type: plugins.DataSource,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Invalid plugin ID",
|
||||
args: args{
|
||||
data: plugins.JSONData{
|
||||
Type: plugins.Panel,
|
||||
},
|
||||
},
|
||||
err: ErrInvalidPluginJSON,
|
||||
},
|
||||
{
|
||||
name: "Invalid plugin type",
|
||||
args: args{
|
||||
data: plugins.JSONData{
|
||||
ID: "grafana-plugin-id",
|
||||
Type: "test",
|
||||
},
|
||||
},
|
||||
err: ErrInvalidPluginJSON,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := validatePluginJSON(tt.args.data); !errors.Is(err, tt.err) {
|
||||
t.Errorf("validatePluginJSON() = %v, want %v", err, tt.err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_setPathsBasedOnApp(t *testing.T) {
|
||||
t.Run("When setting paths based on core plugin on Windows", func(t *testing.T) {
|
||||
child := &plugins.Plugin{
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource",
|
||||
FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app\\datasources\\datasource"),
|
||||
}
|
||||
parent := &plugins.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
Type: plugins.App,
|
||||
ID: "testdata-app",
|
||||
},
|
||||
Class: plugins.Core,
|
||||
PluginDir: "c:\\grafana\\public\\app\\plugins\\app\\testdata-app",
|
||||
BaseURL: "public/app/plugins/app/testdata-app",
|
||||
Class: plugins.Core,
|
||||
FS: fakes.NewFakePluginFiles("c:\\grafana\\public\\app\\plugins\\app\\testdata-app"),
|
||||
BaseURL: "public/app/plugins/app/testdata-app",
|
||||
}
|
||||
|
||||
configureAppChildPlugin(parent, child)
|
||||
@ -1395,8 +1270,8 @@ func verifyState(t *testing.T, ps []*plugins.Plugin, reg *fakes.FakePluginRegist
|
||||
t.Helper()
|
||||
|
||||
for _, p := range ps {
|
||||
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))
|
||||
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...))
|
||||
}
|
||||
|
||||
if p.Backend {
|
||||
@ -1418,3 +1293,40 @@ func verifyState(t *testing.T, ps []*plugins.Plugin, reg *fakes.FakePluginRegist
|
||||
require.Zero(t, procMngr.Stopped[p.ID])
|
||||
}
|
||||
}
|
||||
|
||||
func filesInDir(t *testing.T, dir string) map[string]struct{} {
|
||||
files, err := collectFilesWithin(dir)
|
||||
if err != nil {
|
||||
t.Logf("Could not collect plugin file info. Err: %v", err)
|
||||
return map[string]struct{}{}
|
||||
}
|
||||
return files
|
||||
}
|
||||
|
||||
func collectFilesWithin(dir string) (map[string]struct{}, error) {
|
||||
files := map[string]struct{}{}
|
||||
err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// skip directories
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// verify that file is within plugin directory
|
||||
//file, err := filepath.Rel(dir, path)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
//if strings.HasPrefix(file, ".."+string(filepath.Separator)) {
|
||||
// return fmt.Errorf("file '%s' not inside of plugin directory", file)
|
||||
//}
|
||||
|
||||
files[path] = struct{}{}
|
||||
return nil
|
||||
})
|
||||
|
||||
return files, err
|
||||
}
|
||||
|
Reference in New Issue
Block a user