Files
Will Browne f6ed9e6ff0 Plugins: Introduce plugin asset provider (#108063)
* introduce plugin asset provider

* simply with PR feedback

* fix linter
2025-07-17 16:20:35 +01:00

122 lines
3.6 KiB
Go

package bootstrap
import (
"fmt"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/log"
"github.com/grafana/grafana/pkg/plugins/manager/loader/assetpath"
"github.com/grafana/grafana/pkg/plugins/pluginassets"
)
type pluginFactoryFunc func(p *plugins.FoundBundle, pluginClass plugins.Class, sig plugins.Signature) (*plugins.Plugin, error)
// DefaultPluginFactory is the default plugin factory used by the Construct step of the Bootstrap stage.
//
// It creates the plugin using plugin information found during the Discovery stage and makes use of the assetPath
// service to set the plugin's BaseURL, Module, Logos and Screenshots fields.
type DefaultPluginFactory struct {
assetPath *assetpath.Service
features *config.Features
}
// NewDefaultPluginFactory returns a new DefaultPluginFactory.
func NewDefaultPluginFactory(features *config.Features, assetPath *assetpath.Service) *DefaultPluginFactory {
return &DefaultPluginFactory{assetPath: assetPath, features: features}
}
func (f *DefaultPluginFactory) createPlugin(bundle *plugins.FoundBundle, class plugins.Class,
sig plugins.Signature) (*plugins.Plugin, error) {
parentInfo := pluginassets.NewPluginInfo(bundle.Primary.JSONData, class, bundle.Primary.FS, nil)
plugin, err := f.newPlugin(bundle.Primary, class, sig, parentInfo)
if err != nil {
return nil, err
}
if len(bundle.Children) == 0 {
return plugin, nil
}
plugin.Children = make([]*plugins.Plugin, 0, len(bundle.Children))
for _, child := range bundle.Children {
childInfo := pluginassets.NewPluginInfo(child.JSONData, class, child.FS, &parentInfo)
cp, err := f.newPlugin(*child, class, sig, childInfo)
if err != nil {
return nil, err
}
cp.Parent = plugin
plugin.Children = append(plugin.Children, cp)
}
return plugin, nil
}
func (f *DefaultPluginFactory) newPlugin(p plugins.FoundPlugin, class plugins.Class, sig plugins.Signature,
info pluginassets.PluginInfo) (*plugins.Plugin, error) {
baseURL, err := f.assetPath.Base(info)
if err != nil {
return nil, fmt.Errorf("base url: %w", err)
}
moduleURL, err := f.assetPath.Module(info)
if err != nil {
return nil, fmt.Errorf("module url: %w", err)
}
plugin := &plugins.Plugin{
JSONData: p.JSONData,
Class: class,
FS: p.FS,
BaseURL: baseURL,
Module: moduleURL,
Signature: sig.Status,
SignatureType: sig.Type,
SignatureOrg: sig.SigningOrg,
}
plugin.SetLogger(log.New(fmt.Sprintf("plugin.%s", plugin.ID)))
if err = setImages(plugin, f.assetPath, info); err != nil {
return nil, err
}
if f.features.LocalizationForPlugins {
if err := setTranslations(plugin, f.assetPath, info); err != nil {
return nil, err
}
}
return plugin, nil
}
func setImages(p *plugins.Plugin, assetPath *assetpath.Service, info pluginassets.PluginInfo) error {
var err error
for _, dst := range []*string{&p.Info.Logos.Small, &p.Info.Logos.Large} {
if len(*dst) == 0 {
*dst = assetPath.DefaultLogoPath(p.Type)
continue
}
*dst, err = assetPath.RelativeURL(info, *dst)
if err != nil {
return fmt.Errorf("logo: %w", err)
}
}
for i := 0; i < len(p.Info.Screenshots); i++ {
screenshot := &p.Info.Screenshots[i]
screenshot.Path, err = assetPath.RelativeURL(info, screenshot.Path)
if err != nil {
return fmt.Errorf("screenshot %d relative url: %w", i, err)
}
}
return nil
}
func setTranslations(p *plugins.Plugin, assetPath *assetpath.Service, info pluginassets.PluginInfo) error {
translations, err := assetPath.GetTranslations(info)
if err != nil {
return fmt.Errorf("set translations: %w", err)
}
p.Translations = translations
return nil
}