The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
grafana/pkg/api/plugins.go

446 lines
14 KiB

package api
import (
"encoding/json"
"errors"
"net/http"
"os"
"path/filepath"
"sort"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/backendplugin"
Plugins: Enable plugin runtime install/uninstall capabilities (#33836) * add uninstall flow * add install flow * small cleanup * smaller-footprint solution * cleanup + make bp start auto * fix interface contract * improve naming * accept version arg * ensure use of shared logger * make installer a field * add plugin decommissioning * add basic error checking * fix api docs * making initialization idempotent * add mutex * fix comment * fix test * add test for decommission * improve existing test * add more test coverage * more tests * change test func to use read lock * refactoring + adding test asserts * improve purging old install flow * improve dupe checking * change log name * skip over dupe scanned * make test assertion more flexible * remove trailing line * fix pointer receiver name * update comment * add context to API * add config flag * add base http api test + fix update functionality * simplify existing check * clean up test * refactor tests based on feedback * add single quotes to errs * use gcmp in tests + fix logo issue * make plugin list testing more flexible * address feedback * fix API test * fix linter * undo preallocate * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * fix linting issue in test * add docs placeholder * update install notes * Update docs/sources/plugins/marketplace.md Co-authored-by: Marcus Olsson <marcus.olsson@hey.com> * update access wording * add more placeholder docs * add link to more info * PR feedback - improved errors, refactor, lock fix * improve err details * propagate plugin version errors * don't autostart renderer * add H1 * fix imports Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>
4 years ago
"github.com/grafana/grafana/pkg/plugins/manager/installer"
"github.com/grafana/grafana/pkg/setting"
macaron "gopkg.in/macaron.v1"
)
func (hs *HTTPServer) GetPluginList(c *models.ReqContext) response.Response {
typeFilter := c.Query("type")
enabledFilter := c.Query("enabled")
embeddedFilter := c.Query("embedded")
coreFilter := c.Query("core")
// For users with viewer role we only return core plugins
if !c.HasRole(models.ROLE_ADMIN) {
coreFilter = "1"
}
pluginSettingsMap, err := hs.PluginManager.GetPluginSettings(c.OrgId)
if err != nil {
return response.Error(500, "Failed to get list of plugins", err)
}
result := make(dtos.PluginList, 0)
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
for _, pluginDef := range hs.PluginManager.Plugins() {
// filter out app sub plugins
if embeddedFilter == "0" && pluginDef.IncludedInAppId != "" {
continue
}
// filter out core plugins
if (coreFilter == "0" && pluginDef.IsCorePlugin) || (coreFilter == "1" && !pluginDef.IsCorePlugin) {
continue
}
// filter on type
if typeFilter != "" && typeFilter != pluginDef.Type {
continue
}
if pluginDef.State == plugins.PluginStateAlpha && !hs.Cfg.PluginsEnableAlpha {
continue
}
listItem := dtos.PluginListItem{
Id: pluginDef.Id,
Name: pluginDef.Name,
Type: pluginDef.Type,
Category: pluginDef.Category,
Info: &pluginDef.Info,
LatestVersion: pluginDef.GrafanaNetVersion,
HasUpdate: pluginDef.GrafanaNetHasUpdate,
DefaultNavUrl: pluginDef.DefaultNavUrl,
State: pluginDef.State,
Signature: pluginDef.Signature,
SignatureType: pluginDef.SignatureType,
SignatureOrg: pluginDef.SignatureOrg,
}
if pluginSetting, exists := pluginSettingsMap[pluginDef.Id]; exists {
listItem.Enabled = pluginSetting.Enabled
listItem.Pinned = pluginSetting.Pinned
}
if listItem.DefaultNavUrl == "" || !listItem.Enabled {
listItem.DefaultNavUrl = hs.Cfg.AppSubURL + "/plugins/" + listItem.Id + "/"
}
// filter out disabled plugins
if enabledFilter == "1" && !listItem.Enabled {
continue
}
// filter out built in data sources
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
if ds := hs.PluginManager.GetDataSource(pluginDef.Id); ds != nil {
if ds.BuiltIn {
continue
}
}
result = append(result, listItem)
}
sort.Sort(result)
return response.JSON(200, result)
}
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
func (hs *HTTPServer) GetPluginSettingByID(c *models.ReqContext) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
def := hs.PluginManager.GetPlugin(pluginID)
if def == nil {
return response.Error(404, "Plugin not found, no installed plugin with that id", nil)
}
dto := &dtos.PluginSetting{
Type: def.Type,
Id: def.Id,
Name: def.Name,
Info: &def.Info,
Dependencies: &def.Dependencies,
Includes: def.Includes,
BaseUrl: def.BaseUrl,
Module: def.Module,
DefaultNavUrl: def.DefaultNavUrl,
LatestVersion: def.GrafanaNetVersion,
HasUpdate: def.GrafanaNetHasUpdate,
State: def.State,
Signature: def.Signature,
SignatureType: def.SignatureType,
SignatureOrg: def.SignatureOrg,
}
if app := hs.PluginManager.GetApp(def.Id); app != nil {
dto.Enabled = app.AutoEnabled
dto.Pinned = app.AutoEnabled
}
query := models.GetPluginSettingByIdQuery{PluginId: pluginID, OrgId: c.OrgId}
if err := bus.Dispatch(&query); err != nil {
if !errors.Is(err, models.ErrPluginSettingNotFound) {
return response.Error(500, "Failed to get login settings", nil)
}
} else {
dto.Enabled = query.Result.Enabled
dto.Pinned = query.Result.Pinned
dto.JsonData = query.Result.JsonData
}
return response.JSON(200, dto)
}
func (hs *HTTPServer) UpdatePluginSetting(c *models.ReqContext, cmd models.UpdatePluginSettingCmd) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
if app := hs.PluginManager.GetApp(pluginID); app == nil {
return response.Error(404, "Plugin not installed", nil)
}
cmd.OrgId = c.OrgId
cmd.PluginId = pluginID
if err := bus.Dispatch(&cmd); err != nil {
return response.Error(500, "Failed to update plugin setting", err)
}
return response.Success("Plugin settings updated")
}
func (hs *HTTPServer) GetPluginDashboards(c *models.ReqContext) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
list, err := hs.PluginManager.GetPluginDashboards(c.OrgId, pluginID)
if err != nil {
var notFound plugins.PluginNotFoundError
if errors.As(err, &notFound) {
return response.Error(404, notFound.Error(), nil)
}
return response.Error(500, "Failed to get plugin dashboards", err)
}
return response.JSON(200, list)
}
func (hs *HTTPServer) GetPluginMarkdown(c *models.ReqContext) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
name := macaron.Params(c.Req)[":name"]
content, err := hs.PluginManager.GetPluginMarkdown(pluginID, name)
if err != nil {
var notFound plugins.PluginNotFoundError
if errors.As(err, &notFound) {
return response.Error(404, notFound.Error(), nil)
}
return response.Error(500, "Could not get markdown file", err)
}
// fallback try readme
if len(content) == 0 {
content, err = hs.PluginManager.GetPluginMarkdown(pluginID, "readme")
if err != nil {
return response.Error(501, "Could not get markdown file", err)
}
}
resp := response.Respond(200, content)
resp.SetHeader("Content-Type", "text/plain; charset=utf-8")
return resp
}
func (hs *HTTPServer) ImportDashboard(c *models.ReqContext, apiCmd dtos.ImportDashboardCommand) response.Response {
var err error
if apiCmd.PluginId == "" && apiCmd.Dashboard == nil {
return response.Error(422, "Dashboard must be set", nil)
}
trimDefaults := c.QueryBoolWithDefault("trimdefaults", true)
if trimDefaults && !hs.LoadSchemaService.IsDisabled() {
apiCmd.Dashboard, err = hs.LoadSchemaService.DashboardApplyDefaults(apiCmd.Dashboard)
if err != nil {
return response.Error(500, "Error while applying default value to the dashboard json", err)
}
}
dashInfo, dash, err := hs.PluginManager.ImportDashboard(apiCmd.PluginId, apiCmd.Path, c.OrgId, apiCmd.FolderId,
apiCmd.Dashboard, apiCmd.Overwrite, apiCmd.Inputs, c.SignedInUser, hs.DataService)
if err != nil {
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
return hs.dashboardSaveErrorToApiResponse(err)
}
err = hs.LibraryPanelService.ImportLibraryPanelsForDashboard(c.Req.Context(), c.SignedInUser, dash, apiCmd.FolderId)
if err != nil {
return response.Error(500, "Error while importing library panels", err)
}
err = hs.LibraryPanelService.ConnectLibraryPanelsForDashboard(c.Req.Context(), c.SignedInUser, dash)
if err != nil {
return response.Error(500, "Error while connecting library panels", err)
}
return response.JSON(200, dashInfo)
}
// CollectPluginMetrics collect metrics from a plugin.
//
// /api/plugins/:pluginId/metrics
func (hs *HTTPServer) CollectPluginMetrics(c *models.ReqContext) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
PluginManager: Make Plugins, Renderer and DataSources non-global (#31866) * PluginManager: Make Plugins and DataSources non-global Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Replace outdated command Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix build Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * DashboardService: Ensure it gets constructed with necessary parameters Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove FocusConvey Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Remove dead code Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Undo interface changes Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Backend: Move tsdbifaces.RequestHandler to plugins.DataRequestHandler Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Rename to DataSourceCount Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Consolidate dashboard interfaces into one Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix test Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com> * Fix dashboard integration tests Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
4 years ago
plugin := hs.PluginManager.GetPlugin(pluginID)
if plugin == nil {
return response.Error(404, "Plugin not found", nil)
}
resp, err := hs.BackendPluginManager.CollectMetrics(c.Req.Context(), plugin.Id)
if err != nil {
return translatePluginRequestErrorToAPIError(err)
}
headers := make(http.Header)
headers.Set("Content-Type", "text/plain")
return response.CreateNormalResponse(headers, resp.PrometheusMetrics, http.StatusOK)
}
// getPluginAssets returns public plugin assets (images, JS, etc.)
//
// /public/plugins/:pluginId/*
func (hs *HTTPServer) getPluginAssets(c *models.ReqContext) {
pluginID := macaron.Params(c.Req)[":pluginId"]
plugin := hs.PluginManager.GetPlugin(pluginID)
if plugin == nil {
c.JsonApiErr(404, "Plugin not found", nil)
return
}
requestedFile := filepath.Clean(macaron.Params(c.Req)["*"])
pluginFilePath := filepath.Join(plugin.PluginDir, requestedFile)
if !plugin.IncludedInSignature(requestedFile) {
hs.log.Warn("Access to requested plugin file will be forbidden in upcoming Grafana versions as the file "+
"is not included in the plugin signature", "file", requestedFile)
}
// It's safe to ignore gosec warning G304 since we already clean the requested file path and subsequently
// use this with a prefix of the plugin's directory, which is set during plugin loading
// nolint:gosec
f, err := os.Open(pluginFilePath)
if err != nil {
if os.IsNotExist(err) {
c.JsonApiErr(404, "Plugin file not found", err)
return
}
c.JsonApiErr(500, "Could not open plugin file", err)
return
}
defer func() {
if err := f.Close(); err != nil {
hs.log.Error("Failed to close file", "err", err)
}
}()
fi, err := f.Stat()
if err != nil {
c.JsonApiErr(500, "Plugin file exists but could not open", err)
return
}
if hs.Cfg.Env == setting.Dev {
c.Resp.Header().Set("Cache-Control", "max-age=0, must-revalidate, no-cache")
} else {
c.Resp.Header().Set("Cache-Control", "public, max-age=3600")
}
http.ServeContent(c.Resp, c.Req, pluginFilePath, fi.ModTime(), f)
}
// CheckHealth returns the health of a plugin.
// /api/plugins/:pluginId/health
func (hs *HTTPServer) CheckHealth(c *models.ReqContext) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
pCtx, found, err := hs.PluginContextProvider.Get(pluginID, "", c.SignedInUser, false)
if err != nil {
return response.Error(500, "Failed to get plugin settings", err)
}
if !found {
return response.Error(404, "Plugin not found", nil)
}
resp, err := hs.BackendPluginManager.CheckHealth(c.Req.Context(), pCtx)
if err != nil {
return translatePluginRequestErrorToAPIError(err)
}
payload := map[string]interface{}{
"status": resp.Status.String(),
"message": resp.Message,
}
// Unmarshal JSONDetails if it's not empty.
if len(resp.JSONDetails) > 0 {
var jsonDetails map[string]interface{}
err = json.Unmarshal(resp.JSONDetails, &jsonDetails)
if err != nil {
return response.Error(500, "Failed to unmarshal detailed response from backend plugin", err)
}
payload["details"] = jsonDetails
}
if resp.Status != backend.HealthStatusOk {
return response.JSON(503, payload)
}
return response.JSON(200, payload)
}
// CallResource passes a resource call from a plugin to the backend plugin.
//
// /api/plugins/:pluginId/resources/*
func (hs *HTTPServer) CallResource(c *models.ReqContext) {
pluginID := macaron.Params(c.Req)[":pluginId"]
pCtx, found, err := hs.PluginContextProvider.Get(pluginID, "", c.SignedInUser, false)
if err != nil {
c.JsonApiErr(500, "Failed to get plugin settings", err)
return
}
if !found {
c.JsonApiErr(404, "Plugin not found", nil)
return
}
hs.BackendPluginManager.CallResource(pCtx, c, macaron.Params(c.Req)["*"])
}
func (hs *HTTPServer) GetPluginErrorsList(_ *models.ReqContext) response.Response {
return response.JSON(200, hs.PluginManager.ScanningErrors())
}
Plugins: Enable plugin runtime install/uninstall capabilities (#33836) * add uninstall flow * add install flow * small cleanup * smaller-footprint solution * cleanup + make bp start auto * fix interface contract * improve naming * accept version arg * ensure use of shared logger * make installer a field * add plugin decommissioning * add basic error checking * fix api docs * making initialization idempotent * add mutex * fix comment * fix test * add test for decommission * improve existing test * add more test coverage * more tests * change test func to use read lock * refactoring + adding test asserts * improve purging old install flow * improve dupe checking * change log name * skip over dupe scanned * make test assertion more flexible * remove trailing line * fix pointer receiver name * update comment * add context to API * add config flag * add base http api test + fix update functionality * simplify existing check * clean up test * refactor tests based on feedback * add single quotes to errs * use gcmp in tests + fix logo issue * make plugin list testing more flexible * address feedback * fix API test * fix linter * undo preallocate * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * fix linting issue in test * add docs placeholder * update install notes * Update docs/sources/plugins/marketplace.md Co-authored-by: Marcus Olsson <marcus.olsson@hey.com> * update access wording * add more placeholder docs * add link to more info * PR feedback - improved errors, refactor, lock fix * improve err details * propagate plugin version errors * don't autostart renderer * add H1 * fix imports Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>
4 years ago
func (hs *HTTPServer) InstallPlugin(c *models.ReqContext, dto dtos.InstallPluginCommand) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
Plugins: Enable plugin runtime install/uninstall capabilities (#33836) * add uninstall flow * add install flow * small cleanup * smaller-footprint solution * cleanup + make bp start auto * fix interface contract * improve naming * accept version arg * ensure use of shared logger * make installer a field * add plugin decommissioning * add basic error checking * fix api docs * making initialization idempotent * add mutex * fix comment * fix test * add test for decommission * improve existing test * add more test coverage * more tests * change test func to use read lock * refactoring + adding test asserts * improve purging old install flow * improve dupe checking * change log name * skip over dupe scanned * make test assertion more flexible * remove trailing line * fix pointer receiver name * update comment * add context to API * add config flag * add base http api test + fix update functionality * simplify existing check * clean up test * refactor tests based on feedback * add single quotes to errs * use gcmp in tests + fix logo issue * make plugin list testing more flexible * address feedback * fix API test * fix linter * undo preallocate * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * fix linting issue in test * add docs placeholder * update install notes * Update docs/sources/plugins/marketplace.md Co-authored-by: Marcus Olsson <marcus.olsson@hey.com> * update access wording * add more placeholder docs * add link to more info * PR feedback - improved errors, refactor, lock fix * improve err details * propagate plugin version errors * don't autostart renderer * add H1 * fix imports Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>
4 years ago
err := hs.PluginManager.Install(c.Req.Context(), pluginID, dto.Version)
if err != nil {
var dupeErr plugins.DuplicatePluginError
if errors.As(err, &dupeErr) {
return response.Error(http.StatusConflict, "Plugin already installed", err)
}
var versionUnsupportedErr installer.ErrVersionUnsupported
if errors.As(err, &versionUnsupportedErr) {
return response.Error(http.StatusConflict, "Plugin version not supported", err)
}
var versionNotFoundErr installer.ErrVersionNotFound
if errors.As(err, &versionNotFoundErr) {
return response.Error(http.StatusNotFound, "Plugin version not found", err)
}
var clientError installer.Response4xxError
if errors.As(err, &clientError) {
return response.Error(clientError.StatusCode, clientError.Message, err)
Plugins: Enable plugin runtime install/uninstall capabilities (#33836) * add uninstall flow * add install flow * small cleanup * smaller-footprint solution * cleanup + make bp start auto * fix interface contract * improve naming * accept version arg * ensure use of shared logger * make installer a field * add plugin decommissioning * add basic error checking * fix api docs * making initialization idempotent * add mutex * fix comment * fix test * add test for decommission * improve existing test * add more test coverage * more tests * change test func to use read lock * refactoring + adding test asserts * improve purging old install flow * improve dupe checking * change log name * skip over dupe scanned * make test assertion more flexible * remove trailing line * fix pointer receiver name * update comment * add context to API * add config flag * add base http api test + fix update functionality * simplify existing check * clean up test * refactor tests based on feedback * add single quotes to errs * use gcmp in tests + fix logo issue * make plugin list testing more flexible * address feedback * fix API test * fix linter * undo preallocate * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * fix linting issue in test * add docs placeholder * update install notes * Update docs/sources/plugins/marketplace.md Co-authored-by: Marcus Olsson <marcus.olsson@hey.com> * update access wording * add more placeholder docs * add link to more info * PR feedback - improved errors, refactor, lock fix * improve err details * propagate plugin version errors * don't autostart renderer * add H1 * fix imports Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>
4 years ago
}
if errors.Is(err, plugins.ErrInstallCorePlugin) {
return response.Error(http.StatusForbidden, "Cannot install or change a Core plugin", err)
}
return response.Error(http.StatusInternalServerError, "Failed to install plugin", err)
}
return response.JSON(http.StatusOK, []byte{})
}
func (hs *HTTPServer) UninstallPlugin(c *models.ReqContext) response.Response {
pluginID := macaron.Params(c.Req)[":pluginId"]
Plugins: Enable plugin runtime install/uninstall capabilities (#33836) * add uninstall flow * add install flow * small cleanup * smaller-footprint solution * cleanup + make bp start auto * fix interface contract * improve naming * accept version arg * ensure use of shared logger * make installer a field * add plugin decommissioning * add basic error checking * fix api docs * making initialization idempotent * add mutex * fix comment * fix test * add test for decommission * improve existing test * add more test coverage * more tests * change test func to use read lock * refactoring + adding test asserts * improve purging old install flow * improve dupe checking * change log name * skip over dupe scanned * make test assertion more flexible * remove trailing line * fix pointer receiver name * update comment * add context to API * add config flag * add base http api test + fix update functionality * simplify existing check * clean up test * refactor tests based on feedback * add single quotes to errs * use gcmp in tests + fix logo issue * make plugin list testing more flexible * address feedback * fix API test * fix linter * undo preallocate * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * Update docs/sources/administration/configuration.md Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> * fix linting issue in test * add docs placeholder * update install notes * Update docs/sources/plugins/marketplace.md Co-authored-by: Marcus Olsson <marcus.olsson@hey.com> * update access wording * add more placeholder docs * add link to more info * PR feedback - improved errors, refactor, lock fix * improve err details * propagate plugin version errors * don't autostart renderer * add H1 * fix imports Co-authored-by: achatterjee-grafana <70489351+achatterjee-grafana@users.noreply.github.com> Co-authored-by: Marcus Olsson <marcus.olsson@hey.com>
4 years ago
err := hs.PluginManager.Uninstall(c.Req.Context(), pluginID)
if err != nil {
if errors.Is(err, plugins.ErrPluginNotInstalled) {
return response.Error(http.StatusNotFound, "Plugin not installed", err)
}
if errors.Is(err, plugins.ErrUninstallCorePlugin) {
return response.Error(http.StatusForbidden, "Cannot uninstall a Core plugin", err)
}
if errors.Is(err, plugins.ErrUninstallOutsideOfPluginDir) {
return response.Error(http.StatusForbidden, "Cannot uninstall a plugin outside of the plugins directory", err)
}
return response.Error(http.StatusInternalServerError, "Failed to uninstall plugin", err)
}
return response.JSON(http.StatusOK, []byte{})
}
func translatePluginRequestErrorToAPIError(err error) response.Response {
if errors.Is(err, backendplugin.ErrPluginNotRegistered) {
return response.Error(404, "Plugin not found", err)
}
if errors.Is(err, backendplugin.ErrMethodNotImplemented) {
return response.Error(404, "Not found", err)
}
if errors.Is(err, backendplugin.ErrHealthCheckFailed) {
return response.Error(500, "Plugin health check failed", err)
}
if errors.Is(err, backendplugin.ErrPluginUnavailable) {
return response.Error(503, "Plugin unavailable", err)
}
return response.Error(500, "Plugin request failed", err)
}