|
|
|
@ -13,6 +13,7 @@ import ( |
|
|
|
|
"strings" |
|
|
|
|
"time" |
|
|
|
|
|
|
|
|
|
"github.com/grafana/grafana/pkg/infra/fs" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/log" |
|
|
|
|
"github.com/grafana/grafana/pkg/infra/metrics" |
|
|
|
|
"github.com/grafana/grafana/pkg/plugins/backendplugin" |
|
|
|
@ -43,12 +44,15 @@ type PluginScanner struct { |
|
|
|
|
errors []error |
|
|
|
|
backendPluginManager backendplugin.Manager |
|
|
|
|
cfg *setting.Cfg |
|
|
|
|
requireSigned bool |
|
|
|
|
log log.Logger |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
type PluginManager struct { |
|
|
|
|
BackendPluginManager backendplugin.Manager `inject:""` |
|
|
|
|
Cfg *setting.Cfg `inject:""` |
|
|
|
|
log log.Logger |
|
|
|
|
scanningErrors []error |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func init() { |
|
|
|
@ -73,33 +77,40 @@ func (pm *PluginManager) Init() error { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pm.log.Info("Starting plugin search") |
|
|
|
|
|
|
|
|
|
plugDir := path.Join(setting.StaticRootPath, "app/plugins") |
|
|
|
|
if err := pm.scan(plugDir); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "Failed to scan main plugin directory '%s'", plugDir) |
|
|
|
|
pm.log.Debug("Scanning core plugin directory", "dir", plugDir) |
|
|
|
|
if err := pm.scan(plugDir, false); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "failed to scan core plugin directory '%s'", plugDir) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pm.log.Info("Checking Bundled Plugins") |
|
|
|
|
plugDir = path.Join(setting.HomePath, "plugins-bundled") |
|
|
|
|
if _, err := os.Stat(plugDir); !os.IsNotExist(err) { |
|
|
|
|
if err := pm.scan(plugDir); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "failed to scan bundled plugin directory '%s'", plugDir) |
|
|
|
|
plugDir = pm.Cfg.BundledPluginsPath |
|
|
|
|
pm.log.Debug("Scanning bundled plugins directory", "dir", plugDir) |
|
|
|
|
exists, err := fs.Exists(plugDir) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if exists { |
|
|
|
|
if err := pm.scan(plugDir, false); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "failed to scan bundled plugins directory '%s'", plugDir) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// check if plugins dir exists
|
|
|
|
|
if _, err := os.Stat(setting.PluginsPath); os.IsNotExist(err) { |
|
|
|
|
exists, err = fs.Exists(setting.PluginsPath) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if !exists { |
|
|
|
|
if err = os.MkdirAll(setting.PluginsPath, os.ModePerm); err != nil { |
|
|
|
|
plog.Error("Failed to create plugin dir", "dir", setting.PluginsPath, "error", err) |
|
|
|
|
pm.log.Error("failed to create external plugins directory", "dir", setting.PluginsPath, "error", err) |
|
|
|
|
} else { |
|
|
|
|
plog.Info("Plugin dir created", "dir", setting.PluginsPath) |
|
|
|
|
if err := pm.scan(setting.PluginsPath); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "Failed to scan configured plugin directory '%s'", |
|
|
|
|
setting.PluginsPath) |
|
|
|
|
} |
|
|
|
|
pm.log.Info("External plugins directory created", "directory", setting.PluginsPath) |
|
|
|
|
} |
|
|
|
|
} else { |
|
|
|
|
if err := pm.scan(setting.PluginsPath); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "Failed to scan configured plugin directory '%s'", |
|
|
|
|
pm.log.Debug("Scanning external plugins directory", "dir", setting.PluginsPath) |
|
|
|
|
if err := pm.scan(setting.PluginsPath, true); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "failed to scan external plugins directory '%s'", |
|
|
|
|
setting.PluginsPath) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -163,8 +174,8 @@ func (pm *PluginManager) checkPluginPaths() error { |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := pm.scan(path); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "Failed to scan directory configured for plugin '%s': '%s'", pluginID, path) |
|
|
|
|
if err := pm.scan(path, false); err != nil { |
|
|
|
|
return errutil.Wrapf(err, "failed to scan directory configured for plugin '%s': '%s'", pluginID, path) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -172,11 +183,13 @@ func (pm *PluginManager) checkPluginPaths() error { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// scan a directory for plugins.
|
|
|
|
|
func (pm *PluginManager) scan(pluginDir string) error { |
|
|
|
|
func (pm *PluginManager) scan(pluginDir string, requireSigned bool) error { |
|
|
|
|
scanner := &PluginScanner{ |
|
|
|
|
pluginPath: pluginDir, |
|
|
|
|
backendPluginManager: pm.BackendPluginManager, |
|
|
|
|
cfg: pm.Cfg, |
|
|
|
|
requireSigned: requireSigned, |
|
|
|
|
log: pm.log, |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if err := util.Walk(pluginDir, true, true, scanner.walker); err != nil { |
|
|
|
@ -196,6 +209,7 @@ func (pm *PluginManager) scan(pluginDir string) error { |
|
|
|
|
|
|
|
|
|
if len(scanner.errors) > 0 { |
|
|
|
|
pm.log.Warn("Some plugins failed to load", "errors", scanner.errors) |
|
|
|
|
pm.scanningErrors = scanner.errors |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
@ -229,7 +243,7 @@ func (scanner *PluginScanner) walker(currentPath string, f os.FileInfo, err erro |
|
|
|
|
if f.Name() == "plugin.json" { |
|
|
|
|
err := scanner.loadPluginJson(currentPath) |
|
|
|
|
if err != nil { |
|
|
|
|
log.Error(3, "Plugins: Failed to load plugin json file: %v, err: %v", currentPath, err) |
|
|
|
|
scanner.log.Error("Failed to load plugin", "error", err, "pluginPath", filepath.Dir(currentPath)) |
|
|
|
|
scanner.errors = append(scanner.errors, err) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
@ -252,21 +266,51 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if pluginCommon.Id == "" || pluginCommon.Type == "" { |
|
|
|
|
return errors.New("Did not find type and id property in plugin.json") |
|
|
|
|
return errors.New("did not find type or id properties in plugin.json") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
pluginCommon.PluginDir = filepath.Dir(pluginJsonFilePath) |
|
|
|
|
|
|
|
|
|
// For the time being, we choose to only require back-end plugins to be signed
|
|
|
|
|
if pluginCommon.Backend && scanner.requireSigned { |
|
|
|
|
scanner.log.Debug("Plugin signature required, validating", "pluginID", pluginCommon.Id, |
|
|
|
|
"pluginDir", pluginCommon.PluginDir) |
|
|
|
|
allowUnsigned := false |
|
|
|
|
for _, plug := range scanner.cfg.PluginsAllowUnsigned { |
|
|
|
|
if plug == pluginCommon.Id { |
|
|
|
|
allowUnsigned = true |
|
|
|
|
break |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if sig := GetPluginSignatureState(&pluginCommon); sig != PluginSignatureValid && !allowUnsigned { |
|
|
|
|
switch sig { |
|
|
|
|
case PluginSignatureUnsigned: |
|
|
|
|
return fmt.Errorf("plugin %q is unsigned", pluginCommon.Id) |
|
|
|
|
case PluginSignatureInvalid: |
|
|
|
|
return fmt.Errorf("plugin %q has an invalid signature", pluginCommon.Id) |
|
|
|
|
case PluginSignatureModified: |
|
|
|
|
return fmt.Errorf("plugin %q's signature has been modified", pluginCommon.Id) |
|
|
|
|
default: |
|
|
|
|
return fmt.Errorf("unrecognized plugin signature state %v", sig) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var loader PluginLoader |
|
|
|
|
pluginGoType, exists := PluginTypes[pluginCommon.Type] |
|
|
|
|
if !exists { |
|
|
|
|
return errors.New("Unknown plugin type " + pluginCommon.Type) |
|
|
|
|
return fmt.Errorf("unknown plugin type %q", pluginCommon.Type) |
|
|
|
|
} |
|
|
|
|
loader = reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) |
|
|
|
|
loader := reflect.New(reflect.TypeOf(pluginGoType)).Interface().(PluginLoader) |
|
|
|
|
|
|
|
|
|
// External plugins need a module.js file for SystemJS to load
|
|
|
|
|
if !strings.HasPrefix(pluginJsonFilePath, setting.StaticRootPath) && !scanner.IsBackendOnlyPlugin(pluginCommon.Type) { |
|
|
|
|
module := filepath.Join(filepath.Dir(pluginJsonFilePath), "module.js") |
|
|
|
|
if _, err := os.Stat(module); os.IsNotExist(err) { |
|
|
|
|
plog.Warn("Plugin missing module.js", |
|
|
|
|
exists, err := fs.Exists(module) |
|
|
|
|
if err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if !exists { |
|
|
|
|
scanner.log.Warn("Plugin missing module.js", |
|
|
|
|
"name", pluginCommon.Name, |
|
|
|
|
"warning", "Missing module.js, If you loaded this plugin from git, make sure to compile it.", |
|
|
|
|
"path", module) |
|
|
|
@ -276,6 +320,7 @@ func (scanner *PluginScanner) loadPluginJson(pluginJsonFilePath string) error { |
|
|
|
|
if _, err := reader.Seek(0, 0); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return loader.Load(jsonParser, currentDir, scanner.backendPluginManager) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -290,11 +335,19 @@ func GetPluginMarkdown(pluginId string, name string) ([]byte, error) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
path := filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToUpper(name))) |
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) { |
|
|
|
|
exists, err := fs.Exists(path) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
if !exists { |
|
|
|
|
path = filepath.Join(plug.PluginDir, fmt.Sprintf("%s.md", strings.ToLower(name))) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if _, err := os.Stat(path); os.IsNotExist(err) { |
|
|
|
|
exists, err = fs.Exists(path) |
|
|
|
|
if err != nil { |
|
|
|
|
return nil, err |
|
|
|
|
} |
|
|
|
|
if !exists { |
|
|
|
|
return make([]byte, 0), nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|