* installer -> repo * add semver format checking * add plugin callbacks in test * remove newline * post install only scans new directories * remove unused stuff * everything in own package * add missing cli params * make grafana version part of the API * resolve conflicts * tidy up logger * fix cli and tidy log statements * rename log package * update struct name * fix linter issue * fs -> filestore * reorder imports * alias import * fix test * fix test * inline var * revert jsonc file * make repo dep of manager * actually inject the thing * accept all args for compatability checks * accept compat from store * pass os + arch vals * don't inject fs * tidy up * tidy up * merge with main and tidy fs storage * fix test * fix packages * fix comment + field name * update fs naming * fixed wire * remove unused func * fix mocks * fix storage test * renaming * fix log line * fix test * re-order field * tidying * add test for update with same version * fix wire for CLI * remove use of ioutil * don't pass field * small tidy * ignore code scanning warn * fix testdata link * update lgtm code
177 lines
4.7 KiB
Go
177 lines
4.7 KiB
Go
package manager
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/plugins/repo"
|
|
)
|
|
|
|
func (m *PluginManager) Plugin(ctx context.Context, pluginID string) (plugins.PluginDTO, bool) {
|
|
p, exists := m.plugin(ctx, pluginID)
|
|
if !exists {
|
|
return plugins.PluginDTO{}, false
|
|
}
|
|
|
|
return p.ToDTO(), true
|
|
}
|
|
|
|
func (m *PluginManager) Plugins(ctx context.Context, pluginTypes ...plugins.Type) []plugins.PluginDTO {
|
|
// if no types passed, assume all
|
|
if len(pluginTypes) == 0 {
|
|
pluginTypes = plugins.PluginTypes
|
|
}
|
|
|
|
var requestedTypes = make(map[plugins.Type]struct{})
|
|
for _, pt := range pluginTypes {
|
|
requestedTypes[pt] = struct{}{}
|
|
}
|
|
|
|
pluginsList := make([]plugins.PluginDTO, 0)
|
|
for _, p := range m.availablePlugins(ctx) {
|
|
if _, exists := requestedTypes[p.Type]; exists {
|
|
pluginsList = append(pluginsList, p.ToDTO())
|
|
}
|
|
}
|
|
return pluginsList
|
|
}
|
|
|
|
// plugin finds a plugin with `pluginID` from the registry that is not decommissioned
|
|
func (m *PluginManager) plugin(ctx context.Context, pluginID string) (*plugins.Plugin, bool) {
|
|
p, exists := m.pluginRegistry.Plugin(ctx, pluginID)
|
|
if !exists || p.IsDecommissioned() {
|
|
return nil, false
|
|
}
|
|
|
|
return p, true
|
|
}
|
|
|
|
// availablePlugins returns all non-decommissioned plugins from the registry
|
|
func (m *PluginManager) availablePlugins(ctx context.Context) []*plugins.Plugin {
|
|
var res []*plugins.Plugin
|
|
for _, p := range m.pluginRegistry.Plugins(ctx) {
|
|
if !p.IsDecommissioned() {
|
|
res = append(res, p)
|
|
}
|
|
}
|
|
return res
|
|
}
|
|
|
|
// registeredPlugins returns all registered plugins from the registry
|
|
func (m *PluginManager) registeredPlugins(ctx context.Context) map[string]struct{} {
|
|
pluginsByID := make(map[string]struct{})
|
|
for _, p := range m.pluginRegistry.Plugins(ctx) {
|
|
pluginsByID[p.ID] = struct{}{}
|
|
}
|
|
|
|
return pluginsByID
|
|
}
|
|
|
|
func (m *PluginManager) Add(ctx context.Context, pluginID, version string, opts plugins.CompatOpts) error {
|
|
compatOpts := repo.NewCompatOpts(opts.GrafanaVersion, opts.OS, opts.Arch)
|
|
|
|
var pluginArchive *repo.PluginArchive
|
|
if plugin, exists := m.plugin(ctx, pluginID); exists {
|
|
if !plugin.IsExternalPlugin() {
|
|
return plugins.ErrInstallCorePlugin
|
|
}
|
|
|
|
if plugin.Info.Version == version {
|
|
return plugins.DuplicateError{
|
|
PluginID: plugin.ID,
|
|
ExistingPluginDir: plugin.PluginDir,
|
|
}
|
|
}
|
|
|
|
// get plugin update information to confirm if target update is possible
|
|
dlOpts, err := m.pluginRepo.GetPluginDownloadOptions(ctx, pluginID, version, compatOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if existing plugin version is the same as the target update version
|
|
if dlOpts.Version == plugin.Info.Version {
|
|
return plugins.DuplicateError{
|
|
PluginID: plugin.ID,
|
|
ExistingPluginDir: plugin.PluginDir,
|
|
}
|
|
}
|
|
|
|
if dlOpts.PluginZipURL == "" && dlOpts.Version == "" {
|
|
return fmt.Errorf("could not determine update options for %s", pluginID)
|
|
}
|
|
|
|
// remove existing installation of plugin
|
|
err = m.Remove(ctx, plugin.ID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if dlOpts.PluginZipURL != "" {
|
|
pluginArchive, err = m.pluginRepo.GetPluginArchiveByURL(ctx, dlOpts.PluginZipURL, compatOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
pluginArchive, err = m.pluginRepo.GetPluginArchive(ctx, pluginID, dlOpts.Version, compatOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
} else {
|
|
var err error
|
|
pluginArchive, err = m.pluginRepo.GetPluginArchive(ctx, pluginID, version, compatOpts)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
extractedArchive, err := m.pluginStorage.Add(ctx, pluginID, pluginArchive.File)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// download dependency plugins
|
|
pathsToScan := []string{extractedArchive.Path}
|
|
for _, dep := range extractedArchive.Dependencies {
|
|
m.log.Info("Fetching %s dependencies...", dep.ID)
|
|
d, err := m.pluginRepo.GetPluginArchive(ctx, dep.ID, dep.Version, compatOpts)
|
|
if err != nil {
|
|
return fmt.Errorf("%v: %w", fmt.Sprintf("failed to download plugin %s from repository", dep.ID), err)
|
|
}
|
|
|
|
depArchive, err := m.pluginStorage.Add(ctx, dep.ID, d.File)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
pathsToScan = append(pathsToScan, depArchive.Path)
|
|
}
|
|
|
|
err = m.loadPlugins(context.Background(), plugins.External, pathsToScan...)
|
|
if err != nil {
|
|
m.log.Error("Could not load plugins", "paths", pathsToScan, "err", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (m *PluginManager) Remove(ctx context.Context, pluginID string) error {
|
|
plugin, exists := m.plugin(ctx, pluginID)
|
|
if !exists {
|
|
return plugins.ErrPluginNotInstalled
|
|
}
|
|
|
|
if !plugin.IsExternalPlugin() {
|
|
return plugins.ErrUninstallCorePlugin
|
|
}
|
|
|
|
if err := m.unregisterAndStop(ctx, plugin); err != nil {
|
|
return err
|
|
}
|
|
|
|
return m.pluginStorage.Remove(ctx, plugin.ID)
|
|
}
|