mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
788b9afda3
* 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
251 lines
8.6 KiB
Go
251 lines
8.6 KiB
Go
package pipeline
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"slices"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
|
"github.com/grafana/grafana/pkg/plugins"
|
|
"github.com/grafana/grafana/pkg/plugins/auth"
|
|
"github.com/grafana/grafana/pkg/plugins/config"
|
|
"github.com/grafana/grafana/pkg/plugins/log"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/initialization"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/validation"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/registry"
|
|
"github.com/grafana/grafana/pkg/plugins/manager/signature"
|
|
"github.com/grafana/grafana/pkg/plugins/plugindef"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
|
|
)
|
|
|
|
// ExternalServiceRegistration implements an InitializeFunc for registering external services.
|
|
type ExternalServiceRegistration struct {
|
|
cfg *config.Cfg
|
|
externalServiceRegistry auth.ExternalServiceRegistry
|
|
log log.Logger
|
|
}
|
|
|
|
// ExternalServiceRegistrationStep returns an InitializeFunc for registering external services.
|
|
func ExternalServiceRegistrationStep(cfg *config.Cfg, externalServiceRegistry auth.ExternalServiceRegistry) initialization.InitializeFunc {
|
|
return newExternalServiceRegistration(cfg, externalServiceRegistry).Register
|
|
}
|
|
|
|
func newExternalServiceRegistration(cfg *config.Cfg, serviceRegistry auth.ExternalServiceRegistry) *ExternalServiceRegistration {
|
|
return &ExternalServiceRegistration{
|
|
cfg: cfg,
|
|
externalServiceRegistry: serviceRegistry,
|
|
log: log.New("plugins.external.registration"),
|
|
}
|
|
}
|
|
|
|
// Register registers the external service with the external service registry, if the feature is enabled.
|
|
func (r *ExternalServiceRegistration) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
|
if p.IAM != nil {
|
|
s, err := r.externalServiceRegistry.RegisterExternalService(ctx, p.ID, plugindef.Type(p.Type), p.IAM)
|
|
if err != nil {
|
|
r.log.Error("Could not register an external service. Initialization skipped", "pluginId", p.ID, "error", err)
|
|
return nil, err
|
|
}
|
|
p.ExternalService = s
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// RegisterPluginRoles implements an InitializeFunc for registering plugin roles.
|
|
type RegisterPluginRoles struct {
|
|
log log.Logger
|
|
roleRegistry plugins.RoleRegistry
|
|
}
|
|
|
|
// RegisterPluginRolesStep returns a new InitializeFunc for registering plugin roles.
|
|
func RegisterPluginRolesStep(roleRegistry plugins.RoleRegistry) initialization.InitializeFunc {
|
|
return newRegisterPluginRoles(roleRegistry).Register
|
|
}
|
|
|
|
func newRegisterPluginRoles(registry plugins.RoleRegistry) *RegisterPluginRoles {
|
|
return &RegisterPluginRoles{
|
|
log: log.New("plugins.roles.registration"),
|
|
roleRegistry: registry,
|
|
}
|
|
}
|
|
|
|
// Register registers the plugin roles with the role registry.
|
|
func (r *RegisterPluginRoles) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
|
if err := r.roleRegistry.DeclarePluginRoles(ctx, p.ID, p.Name, p.Roles); err != nil {
|
|
r.log.Warn("Declare plugin roles failed.", "pluginId", p.ID, "error", err)
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// ReportBuildMetrics reports build information for all plugins, except core and bundled plugins.
|
|
func ReportBuildMetrics(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
|
|
if !p.IsCorePlugin() && !p.IsBundledPlugin() {
|
|
metrics.SetPluginBuildInformation(p.ID, string(p.Type), p.Info.Version, string(p.Signature))
|
|
}
|
|
return p, nil
|
|
}
|
|
|
|
// SignatureValidation implements a ValidateFunc for validating plugin signatures.
|
|
type SignatureValidation struct {
|
|
signatureValidator signature.Validator
|
|
errs pluginerrs.SignatureErrorTracker
|
|
log log.Logger
|
|
}
|
|
|
|
// SignatureValidationStep returns a new ValidateFunc for validating plugin signatures.
|
|
func SignatureValidationStep(signatureValidator signature.Validator,
|
|
sigErr pluginerrs.SignatureErrorTracker) validation.ValidateFunc {
|
|
sv := &SignatureValidation{
|
|
errs: sigErr,
|
|
signatureValidator: signatureValidator,
|
|
log: log.New("plugins.signature.validation"),
|
|
}
|
|
return sv.Validate
|
|
}
|
|
|
|
// Validate validates the plugin signature. If a signature error is encountered, the error is recorded with the
|
|
// pluginerrs.SignatureErrorTracker.
|
|
func (v *SignatureValidation) Validate(ctx context.Context, p *plugins.Plugin) error {
|
|
err := v.signatureValidator.ValidateSignature(p)
|
|
if err != nil {
|
|
var sigErr *plugins.SignatureError
|
|
if errors.As(err, &sigErr) {
|
|
v.log.Warn("Skipping loading plugin due to problem with signature",
|
|
"pluginId", p.ID, "status", sigErr.SignatureStatus)
|
|
p.SignatureError = sigErr
|
|
v.errs.Record(ctx, sigErr)
|
|
}
|
|
return err
|
|
}
|
|
|
|
// clear plugin error if a pre-existing error has since been resolved
|
|
v.errs.Clear(ctx, p.ID)
|
|
|
|
return nil
|
|
}
|
|
|
|
// DisablePlugins is a filter step that will filter out any configured plugins
|
|
type DisablePlugins struct {
|
|
log log.Logger
|
|
cfg *config.Cfg
|
|
}
|
|
|
|
// NewDisablePluginsStep returns a new DisablePlugins.
|
|
func NewDisablePluginsStep(cfg *config.Cfg) *DisablePlugins {
|
|
return &DisablePlugins{
|
|
cfg: cfg,
|
|
log: log.New("plugins.disable"),
|
|
}
|
|
}
|
|
|
|
// Filter will filter out any plugins that are marked to be disabled.
|
|
func (c *DisablePlugins) Filter(bundles []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
|
if len(c.cfg.DisablePlugins) == 0 {
|
|
return bundles, nil
|
|
}
|
|
|
|
disablePluginsMap := make(map[string]bool)
|
|
for _, pluginID := range c.cfg.DisablePlugins {
|
|
disablePluginsMap[pluginID] = true
|
|
}
|
|
|
|
res := []*plugins.FoundBundle{}
|
|
for _, bundle := range bundles {
|
|
if disablePluginsMap[bundle.Primary.JSONData.ID] {
|
|
c.log.Debug("Disabling plugin load", "pluginID", bundle.Primary.JSONData.ID)
|
|
} else {
|
|
res = append(res, bundle)
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// AsExternal is a filter step that will skip loading a core plugin to use an external one.
|
|
type AsExternal struct {
|
|
log log.Logger
|
|
cfg *config.Cfg
|
|
}
|
|
|
|
// NewDisablePluginsStep returns a new DisablePlugins.
|
|
func NewAsExternalStep(cfg *config.Cfg) *AsExternal {
|
|
return &AsExternal{
|
|
cfg: cfg,
|
|
log: log.New("plugins.asExternal"),
|
|
}
|
|
}
|
|
|
|
// Filter will filter out any plugins that are marked to be disabled.
|
|
func (c *AsExternal) Filter(cl plugins.Class, bundles []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
|
if c.cfg.Features == nil || !c.cfg.Features.IsEnabledGlobally(featuremgmt.FlagExternalCorePlugins) {
|
|
return bundles, nil
|
|
}
|
|
|
|
if cl == plugins.ClassCore {
|
|
res := []*plugins.FoundBundle{}
|
|
for _, bundle := range bundles {
|
|
pluginCfg := c.cfg.PluginSettings[bundle.Primary.JSONData.ID]
|
|
// Skip core plugins if the feature flag is enabled and the plugin is in the skip list.
|
|
// It could be loaded later as an external plugin.
|
|
if pluginCfg["as_external"] == "true" {
|
|
c.log.Debug("Skipping the core plugin load", "pluginID", bundle.Primary.JSONData.ID)
|
|
} else {
|
|
res = append(res, bundle)
|
|
}
|
|
}
|
|
return res, nil
|
|
}
|
|
return bundles, nil
|
|
}
|
|
|
|
// DuplicatePluginIDValidation is a filter step that will filter out any plugins that are already registered with the same
|
|
// plugin ID. This includes both the primary plugin and child plugins, which are matched using the plugin.json plugin
|
|
// ID field.
|
|
type DuplicatePluginIDValidation struct {
|
|
registry registry.Service
|
|
log log.Logger
|
|
}
|
|
|
|
// NewDuplicatePluginIDFilterStep returns a new DuplicatePluginIDValidation.
|
|
func NewDuplicatePluginIDFilterStep(registry registry.Service) *DuplicatePluginIDValidation {
|
|
return &DuplicatePluginIDValidation{
|
|
registry: registry,
|
|
log: log.New("plugins.dedupe"),
|
|
}
|
|
}
|
|
|
|
// Filter will filter out any plugins that have already been registered under the same plugin ID.
|
|
func (d *DuplicatePluginIDValidation) Filter(ctx context.Context, bundles []*plugins.FoundBundle) ([]*plugins.FoundBundle, error) {
|
|
res := make([]*plugins.FoundBundle, 0, len(bundles))
|
|
|
|
var matchesPluginIDFunc = func(fp plugins.FoundPlugin) func(p *plugins.Plugin) bool {
|
|
return func(p *plugins.Plugin) bool {
|
|
return p.ID == fp.JSONData.ID
|
|
}
|
|
}
|
|
|
|
for _, b := range bundles {
|
|
ps := d.registry.Plugins(ctx)
|
|
|
|
if slices.ContainsFunc(ps, matchesPluginIDFunc(b.Primary)) {
|
|
d.log.Warn("Skipping loading of plugin as it's a duplicate", "pluginId", b.Primary.JSONData.ID)
|
|
continue
|
|
}
|
|
|
|
var nonDupeChildren []*plugins.FoundPlugin
|
|
for _, child := range b.Children {
|
|
if slices.ContainsFunc(ps, matchesPluginIDFunc(*child)) {
|
|
d.log.Warn("Skipping loading of child plugin as it's a duplicate", "pluginId", child.JSONData.ID)
|
|
continue
|
|
}
|
|
nonDupeChildren = append(nonDupeChildren, child)
|
|
}
|
|
res = append(res, &plugins.FoundBundle{
|
|
Primary: b.Primary,
|
|
Children: nonDupeChildren,
|
|
})
|
|
}
|
|
|
|
return res, nil
|
|
}
|