Plugins: Make it possible to support multiple plugin versions (#82116)
* first pass * use version in more places * add comment * update installer * fix wire * fix tests * tidy * simplify changes * fix in mem * remove unused step * fix step dupe logic for child plugins + add tests
This commit is contained in:
+1
-1
@@ -30,7 +30,7 @@ func (pm *fakePluginInstaller) Add(_ context.Context, pluginID, version string,
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pm *fakePluginInstaller) Remove(_ context.Context, pluginID string) error {
|
||||
func (pm *fakePluginInstaller) Remove(_ context.Context, pluginID, _ string) error {
|
||||
delete(pm.plugins, pluginID)
|
||||
return nil
|
||||
}
|
||||
|
||||
+16
-7
@@ -274,7 +274,12 @@ func (hs *HTTPServer) GetPluginMarkdown(c *contextmodel.ReqContext) response.Res
|
||||
pluginID := web.Params(c.Req)[":pluginId"]
|
||||
name := web.Params(c.Req)[":name"]
|
||||
|
||||
content, err := hs.pluginMarkdown(c.Req.Context(), pluginID, name)
|
||||
p, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID)
|
||||
if !exists {
|
||||
return response.Error(http.StatusNotFound, "Plugin not installed", nil)
|
||||
}
|
||||
|
||||
content, err := hs.pluginMarkdown(c.Req.Context(), pluginID, p.Info.Version, name)
|
||||
if err != nil {
|
||||
var notFound plugins.NotFoundError
|
||||
if errors.As(err, ¬Found) {
|
||||
@@ -286,7 +291,7 @@ func (hs *HTTPServer) GetPluginMarkdown(c *contextmodel.ReqContext) response.Res
|
||||
|
||||
// fallback try readme
|
||||
if len(content) == 0 {
|
||||
content, err = hs.pluginMarkdown(c.Req.Context(), pluginID, "readme")
|
||||
content, err = hs.pluginMarkdown(c.Req.Context(), pluginID, p.Info.Version, "readme")
|
||||
if err != nil {
|
||||
if errors.Is(err, plugins.ErrFileNotExist) {
|
||||
return response.Error(http.StatusNotFound, plugins.ErrFileNotExist.Error(), nil)
|
||||
@@ -354,7 +359,7 @@ func (hs *HTTPServer) getPluginAssets(c *contextmodel.ReqContext) {
|
||||
|
||||
// serveLocalPluginAsset returns the content of a plugin asset file from the local filesystem to the http client.
|
||||
func (hs *HTTPServer) serveLocalPluginAsset(c *contextmodel.ReqContext, plugin pluginstore.Plugin, assetPath string) {
|
||||
f, err := hs.pluginFileStore.File(c.Req.Context(), plugin.ID, assetPath)
|
||||
f, err := hs.pluginFileStore.File(c.Req.Context(), plugin.ID, plugin.Info.Version, assetPath)
|
||||
if err != nil {
|
||||
if errors.Is(err, plugins.ErrFileNotExist) {
|
||||
c.JsonApiErr(404, "Plugin file not found", nil)
|
||||
@@ -476,8 +481,12 @@ func (hs *HTTPServer) InstallPlugin(c *contextmodel.ReqContext) response.Respons
|
||||
|
||||
func (hs *HTTPServer) UninstallPlugin(c *contextmodel.ReqContext) response.Response {
|
||||
pluginID := web.Params(c.Req)[":pluginId"]
|
||||
plugin, exists := hs.pluginStore.Plugin(c.Req.Context(), pluginID)
|
||||
if !exists {
|
||||
return response.Error(http.StatusNotFound, "Plugin not installed", nil)
|
||||
}
|
||||
|
||||
err := hs.pluginInstaller.Remove(c.Req.Context(), pluginID)
|
||||
err := hs.pluginInstaller.Remove(c.Req.Context(), pluginID, plugin.Info.Version)
|
||||
if err != nil {
|
||||
if errors.Is(err, plugins.ErrPluginNotInstalled) {
|
||||
return response.Error(http.StatusNotFound, "Plugin not installed", err)
|
||||
@@ -494,19 +503,19 @@ func translatePluginRequestErrorToAPIError(err error) response.Response {
|
||||
return response.ErrOrFallback(http.StatusInternalServerError, "Plugin request failed", err)
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) pluginMarkdown(ctx context.Context, pluginID string, name string) ([]byte, error) {
|
||||
func (hs *HTTPServer) pluginMarkdown(ctx context.Context, pluginID, pluginVersion, name string) ([]byte, error) {
|
||||
file, err := mdFilepath(strings.ToUpper(name))
|
||||
if err != nil {
|
||||
return make([]byte, 0), err
|
||||
}
|
||||
|
||||
md, err := hs.pluginFileStore.File(ctx, pluginID, file)
|
||||
md, err := hs.pluginFileStore.File(ctx, pluginID, pluginVersion, file)
|
||||
if err != nil {
|
||||
if errors.Is(err, plugins.ErrPluginNotInstalled) {
|
||||
return make([]byte, 0), plugins.NotFoundError{PluginID: pluginID}
|
||||
}
|
||||
|
||||
md, err = hs.pluginFileStore.File(ctx, pluginID, strings.ToLower(file))
|
||||
md, err = hs.pluginFileStore.File(ctx, pluginID, pluginVersion, strings.ToLower(file))
|
||||
if err != nil {
|
||||
return make([]byte, 0), nil
|
||||
}
|
||||
|
||||
+19
-11
@@ -49,6 +49,7 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
canInstall := []ac.Permission{{Action: pluginaccesscontrol.ActionInstall}}
|
||||
cannotInstall := []ac.Permission{{Action: "plugins:cannotinstall"}}
|
||||
|
||||
pluginID := "grafana-test-datasource"
|
||||
localOrg := int64(1)
|
||||
globalOrg := int64(ac.GlobalOrgID)
|
||||
|
||||
@@ -88,11 +89,17 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
|
||||
hs.pluginInstaller = NewFakePluginInstaller()
|
||||
hs.pluginFileStore = &fakes.FakePluginFileStore{}
|
||||
hs.pluginStore = pluginstore.NewFakePluginStore(pluginstore.Plugin{
|
||||
JSONData: plugins.JSONData{
|
||||
ID: pluginID,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run(testName("Install", tc), func(t *testing.T) {
|
||||
input := strings.NewReader(`{"version": "1.0.2"}`)
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/plugins/test/install", input), userWithPermissions(tc.permissionOrg, tc.permissions))
|
||||
endpoint := fmt.Sprintf("/api/plugins/%s/install", pluginID)
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest(endpoint, input), userWithPermissions(tc.permissionOrg, tc.permissions))
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedCode, res.StatusCode)
|
||||
@@ -101,7 +108,8 @@ func Test_PluginsInstallAndUninstall(t *testing.T) {
|
||||
|
||||
t.Run(testName("Uninstall", tc), func(t *testing.T) {
|
||||
input := strings.NewReader("{ }")
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest("/api/plugins/test/uninstall", input), userWithPermissions(tc.permissionOrg, tc.permissions))
|
||||
endpoint := fmt.Sprintf("/api/plugins/%s/uninstall", pluginID)
|
||||
req := webtest.RequestWithSignedInUser(server.NewPostRequest(endpoint, input), userWithPermissions(tc.permissionOrg, tc.permissions))
|
||||
res, err := server.SendJSON(req)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, tc.expectedCode, res.StatusCode)
|
||||
@@ -401,14 +409,14 @@ func TestMakePluginResourceRequestContentTypeEmpty(t *testing.T) {
|
||||
func TestPluginMarkdown(t *testing.T) {
|
||||
t.Run("Plugin not installed returns error", func(t *testing.T) {
|
||||
pluginFileStore := &fakes.FakePluginFileStore{
|
||||
FileFunc: func(ctx context.Context, pluginID, filename string) (*plugins.File, error) {
|
||||
FileFunc: func(ctx context.Context, pluginID, pluginVersion, filename string) (*plugins.File, error) {
|
||||
return nil, plugins.ErrPluginNotInstalled
|
||||
},
|
||||
}
|
||||
hs := HTTPServer{pluginFileStore: pluginFileStore}
|
||||
|
||||
pluginID := "test-datasource"
|
||||
md, err := hs.pluginMarkdown(context.Background(), pluginID, "test")
|
||||
md, err := hs.pluginMarkdown(context.Background(), pluginID, "", "test")
|
||||
require.ErrorAs(t, err, &plugins.NotFoundError{PluginID: pluginID})
|
||||
require.Equal(t, []byte{}, md)
|
||||
})
|
||||
@@ -416,7 +424,7 @@ func TestPluginMarkdown(t *testing.T) {
|
||||
t.Run("File fetch will be retried using different casing if error occurs", func(t *testing.T) {
|
||||
var requestedFiles []string
|
||||
pluginFileStore := &fakes.FakePluginFileStore{
|
||||
FileFunc: func(ctx context.Context, pluginID, filename string) (*plugins.File, error) {
|
||||
FileFunc: func(ctx context.Context, pluginID, pluginVersion, filename string) (*plugins.File, error) {
|
||||
requestedFiles = append(requestedFiles, filename)
|
||||
return nil, errors.New("some error")
|
||||
},
|
||||
@@ -424,7 +432,7 @@ func TestPluginMarkdown(t *testing.T) {
|
||||
|
||||
hs := HTTPServer{pluginFileStore: pluginFileStore}
|
||||
|
||||
md, err := hs.pluginMarkdown(context.Background(), "", "reAdMe")
|
||||
md, err := hs.pluginMarkdown(context.Background(), "", "", "reAdMe")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []byte{}, md)
|
||||
require.Equal(t, []string{"README.md", "readme.md"}, requestedFiles)
|
||||
@@ -453,7 +461,7 @@ func TestPluginMarkdown(t *testing.T) {
|
||||
data := []byte{123}
|
||||
var requestedFiles []string
|
||||
pluginFileStore := &fakes.FakePluginFileStore{
|
||||
FileFunc: func(ctx context.Context, pluginID, filename string) (*plugins.File, error) {
|
||||
FileFunc: func(ctx context.Context, pluginID, pluginVersion, filename string) (*plugins.File, error) {
|
||||
requestedFiles = append(requestedFiles, filename)
|
||||
return &plugins.File{Content: data}, nil
|
||||
},
|
||||
@@ -461,7 +469,7 @@ func TestPluginMarkdown(t *testing.T) {
|
||||
|
||||
hs := HTTPServer{pluginFileStore: pluginFileStore}
|
||||
|
||||
md, err := hs.pluginMarkdown(context.Background(), "test-datasource", tc.filePath)
|
||||
md, err := hs.pluginMarkdown(context.Background(), "test-datasource", "", tc.filePath)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, md)
|
||||
require.Equal(t, tc.expected, requestedFiles)
|
||||
@@ -471,7 +479,7 @@ func TestPluginMarkdown(t *testing.T) {
|
||||
t.Run("Non markdown file request returns an error", func(t *testing.T) {
|
||||
hs := HTTPServer{pluginFileStore: &fakes.FakePluginFileStore{}}
|
||||
|
||||
md, err := hs.pluginMarkdown(context.Background(), "", "test.json")
|
||||
md, err := hs.pluginMarkdown(context.Background(), "", "", "test.json")
|
||||
require.ErrorIs(t, err, ErrUnexpectedFileExtension)
|
||||
require.Equal(t, []byte{}, md)
|
||||
})
|
||||
@@ -480,14 +488,14 @@ func TestPluginMarkdown(t *testing.T) {
|
||||
data := []byte{1, 2, 3}
|
||||
|
||||
pluginFileStore := &fakes.FakePluginFileStore{
|
||||
FileFunc: func(ctx context.Context, pluginID, filename string) (*plugins.File, error) {
|
||||
FileFunc: func(ctx context.Context, pluginID, pluginVersion, filename string) (*plugins.File, error) {
|
||||
return &plugins.File{Content: data}, nil
|
||||
},
|
||||
}
|
||||
|
||||
hs := HTTPServer{pluginFileStore: pluginFileStore}
|
||||
|
||||
md, err := hs.pluginMarkdown(context.Background(), "", "someFile")
|
||||
md, err := hs.pluginMarkdown(context.Background(), "", "", "someFile")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, data, md)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user