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/services/pluginsintegration/pipeline/steps.go

296 lines
10 KiB

package pipeline
import (
"context"
"errors"
"fmt"
"slices"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"github.com/grafana/grafana/pkg/infra/metrics"
"github.com/grafana/grafana/pkg/infra/tracing"
"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/services/pluginsintegration/pluginaccesscontrol"
)
// ExternalServiceRegistration implements an InitializeFunc for registering external services.
type ExternalServiceRegistration struct {
cfg *config.PluginManagementCfg
externalServiceRegistry auth.ExternalServiceRegistry
log log.Logger
tracer tracing.Tracer
}
// ExternalServiceRegistrationStep returns an InitializeFunc for registering external services.
func ExternalServiceRegistrationStep(cfg *config.PluginManagementCfg, externalServiceRegistry auth.ExternalServiceRegistry, tracer tracing.Tracer) initialization.InitializeFunc {
return newExternalServiceRegistration(cfg, externalServiceRegistry, tracer).Register
}
func newExternalServiceRegistration(cfg *config.PluginManagementCfg, serviceRegistry auth.ExternalServiceRegistry, tracer tracing.Tracer) *ExternalServiceRegistration {
return &ExternalServiceRegistration{
cfg: cfg,
externalServiceRegistry: serviceRegistry,
log: log.New("plugins.external.registration"),
tracer: tracer,
}
}
// 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 {
return p, nil
}
ctx, span := r.tracer.Start(ctx, "ExternalServiceRegistration.Register")
span.SetAttributes(attribute.String("register.pluginId", p.ID))
defer span.End()
ctxLogger := r.log.FromContext(ctx)
s, err := r.externalServiceRegistry.RegisterExternalService(ctx, p.ID, string(p.Type), p.IAM)
if err != nil {
ctxLogger.Error("Could not register an external service. Initialization skipped", "pluginId", p.ID, "error", err)
span.SetStatus(codes.Error, fmt.Sprintf("could not register external service: %v", 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 pluginaccesscontrol.RoleRegistry
}
// RegisterPluginRolesStep returns a new InitializeFunc for registering plugin roles.
func RegisterPluginRolesStep(roleRegistry pluginaccesscontrol.RoleRegistry) initialization.InitializeFunc {
return newRegisterPluginRoles(roleRegistry).Register
}
func newRegisterPluginRoles(registry pluginaccesscontrol.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 nil, err
}
return p, nil
}
// RegisterActionSets implements an InitializeFunc for registering plugin action sets.
type RegisterActionSets struct {
log log.Logger
actionSetRegistry pluginaccesscontrol.ActionSetRegistry
}
// RegisterActionSetsStep returns a new InitializeFunc for registering plugin action sets.
func RegisterActionSetsStep(actionRegistry pluginaccesscontrol.ActionSetRegistry) initialization.InitializeFunc {
return newRegisterActionSets(actionRegistry).Register
}
func newRegisterActionSets(registry pluginaccesscontrol.ActionSetRegistry) *RegisterActionSets {
return &RegisterActionSets{
log: log.New("plugins.actionsets.registration"),
actionSetRegistry: registry,
}
}
// Register registers the plugin action sets.
func (r *RegisterActionSets) Register(ctx context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
if err := r.actionSetRegistry.RegisterActionSets(ctx, p.ID, p.ActionSets); err != nil {
r.log.Warn("Plugin action set registration failed", "pluginId", p.ID, "error", err)
return nil, err
}
return p, nil
}
// ReportBuildMetrics reports build information for all plugins, except core plugins.
func ReportBuildMetrics(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
if !p.IsCorePlugin() {
metrics.SetPluginBuildInformation(p.ID, string(p.Type), p.Info.Version, string(p.Signature))
}
return p, nil
}
// ReportTargetMetrics reports target information for all backend plugins.
func ReportTargetMetrics(_ context.Context, p *plugins.Plugin) (*plugins.Plugin, error) {
if p.Backend {
metrics.SetPluginTargetInformation(p.ID, string(p.Target()))
}
return p, nil
}
// SignatureValidation implements a ValidateFunc for validating plugin signatures.
type SignatureValidation struct {
signatureValidator signature.Validator
log log.Logger
}
// SignatureValidationStep returns a new ValidateFunc for validating plugin signatures.
func SignatureValidationStep(signatureValidator signature.Validator) validation.ValidateFunc {
sv := &SignatureValidation{
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.ErrorTracker.
func (v *SignatureValidation) Validate(ctx context.Context, p *plugins.Plugin) error {
err := v.signatureValidator.ValidateSignature(p)
if err != nil {
var sigErr *plugins.Error
if errors.As(err, &sigErr) {
v.log.Warn("Skipping loading plugin due to problem with signature",
"pluginId", p.ID, "status", sigErr.SignatureStatus)
p.Error = sigErr
}
return err
}
return nil
}
// DisablePlugins is a filter step that will filter out any configured plugins
type DisablePlugins struct {
log log.Logger
cfg *config.PluginManagementCfg
}
// NewDisablePluginsStep returns a new DisablePlugins.
func NewDisablePluginsStep(cfg *config.PluginManagementCfg) *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.PluginManagementCfg
}
// NewAsExternalStep returns a new DisablePlugins.
func NewAsExternalStep(cfg *config.PluginManagementCfg) *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.ExternalCorePluginsEnabled {
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
}