This commit is contained in:
Will Browne
2025-12-11 15:34:55 +00:00
parent 32d43f5b5d
commit 040dbfb5e3
5 changed files with 127 additions and 118 deletions
+4 -4
View File
@@ -87,7 +87,7 @@ func (p *CatalogProvider) GetMeta(ctx context.Context, pluginID, version string)
return nil, fmt.Errorf("failed to decode response: %w", err)
}
spec := pluginsv0alpha1.MetaSpec{
metaSpec := pluginsv0alpha1.MetaSpec{
PluginJson: gcomMeta.JSON,
}
@@ -118,16 +118,16 @@ func (p *CatalogProvider) GetMeta(ctx context.Context, pluginID, version string)
signature.Org = &gcomMeta.SignedByOrg
}
spec.Signature = signature
metaSpec.Signature = signature
}
// Set angular info
spec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
metaSpec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
Detected: gcomMeta.AngularDetected,
}
return &Result{
Meta: spec,
Meta: metaSpec,
TTL: p.ttl,
}, nil
}
+8 -8
View File
@@ -488,7 +488,7 @@ func jsonDataToPluginMetaJSONData(jsonData plugins.JSONData) pluginsv0alpha1.Met
// This is similar to pluginToPluginMetaSpec but works with the plugin store DTO.
// loadingStrategy and moduleHash are optional calculated values that can be provided.
func pluginStorePluginToMeta(plugin pluginstore.Plugin, loadingStrategy plugins.LoadingStrategy, moduleHash string) pluginsv0alpha1.MetaSpec {
spec := pluginsv0alpha1.MetaSpec{
metaSpec := pluginsv0alpha1.MetaSpec{
PluginJson: jsonDataToPluginMetaJSONData(plugin.JSONData),
}
@@ -509,11 +509,11 @@ func pluginStorePluginToMeta(plugin pluginstore.Plugin, loadingStrategy plugins.
}
module.LoadingStrategy = &ls
}
spec.Module = module
metaSpec.Module = module
}
if plugin.BaseURL != "" {
spec.BaseURL = &plugin.BaseURL
metaSpec.BaseURL = &plugin.BaseURL
}
if plugin.Signature != "" {
@@ -530,22 +530,22 @@ func pluginStorePluginToMeta(plugin pluginstore.Plugin, loadingStrategy plugins.
signature.Org = &plugin.SignatureOrg
}
spec.Signature = signature
metaSpec.Signature = signature
}
if len(plugin.Children) > 0 {
spec.Children = plugin.Children
metaSpec.Children = plugin.Children
}
spec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
metaSpec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
Detected: plugin.Angular.Detected,
}
if len(plugin.Translations) > 0 {
spec.Translations = plugin.Translations
metaSpec.Translations = plugin.Translations
}
return spec
return metaSpec
}
// convertSignatureStatus converts plugins.SignatureStatus to pluginsv0alpha1.MetaV0alpha1SpecSignatureStatus.
+110 -16
View File
@@ -13,7 +13,15 @@ import (
pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/plugins/config"
pluginsLoader "github.com/grafana/grafana/pkg/plugins/manager/loader"
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/bootstrap"
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/discovery"
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/initialization"
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/termination"
"github.com/grafana/grafana/pkg/plugins/manager/pipeline/validation"
"github.com/grafana/grafana/pkg/plugins/manager/sources"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginerrs"
)
const (
@@ -26,6 +34,7 @@ type CoreProvider struct {
loadedPlugins map[string]pluginsv0alpha1.MetaSpec
initialized bool
ttl time.Duration
loader pluginsLoader.Service
}
// NewCoreProvider creates a new CoreProvider for core plugins.
@@ -35,9 +44,13 @@ func NewCoreProvider() *CoreProvider {
// NewCoreProviderWithTTL creates a new CoreProvider with a custom TTL.
func NewCoreProviderWithTTL(ttl time.Duration) *CoreProvider {
cfg := &config.PluginManagementCfg{
Features: config.Features{},
}
return &CoreProvider{
loadedPlugins: make(map[string]pluginsv0alpha1.MetaSpec),
ttl: ttl,
loader: createLoader(cfg),
}
}
@@ -86,8 +99,8 @@ func (p *CoreProvider) GetMeta(ctx context.Context, pluginID, _ string) (*Result
return nil, ErrMetaNotFound
}
// loadPlugins discovers and caches all core plugins.
// Returns an error if the static root path cannot be found or if plugin discovery fails.
// loadPlugins discovers and caches all core plugins by fully loading them.
// Returns an error if the static root path cannot be found or if plugin loading fails.
// This error will be handled gracefully by GetMeta, which will return ErrMetaNotFound
// to allow other providers to handle the request.
func (p *CoreProvider) loadPlugins(ctx context.Context) error {
@@ -108,32 +121,113 @@ func (p *CoreProvider) loadPlugins(ctx context.Context) error {
panelPath := filepath.Join(staticRootPath, "app", "plugins", "panel")
src := sources.NewLocalSource(plugins.ClassCore, []string{datasourcePath, panelPath})
ps, err := src.Discover(ctx)
loadedPlugins, err := p.loader.Load(ctx, src)
if err != nil {
return err
}
if len(ps) == 0 {
logging.DefaultLogger.Warn("CoreProvider: no core plugins found during discovery")
if len(loadedPlugins) == 0 {
logging.DefaultLogger.Warn("CoreProvider: no core plugins found during loading")
return nil
}
for _, bundle := range ps {
spec := pluginsv0alpha1.MetaSpec{
PluginJson: jsonDataToMetaJSONData(bundle.Primary.JSONData),
Module: nil,
BaseURL: nil,
Signature: nil,
Angular: nil,
Translations: nil,
Children: nil,
}
p.loadedPlugins[bundle.Primary.JSONData.ID] = spec
for _, plugin := range loadedPlugins {
metaSpec := p.pluginToMetaSpec(plugin)
p.loadedPlugins[plugin.ID] = metaSpec
}
return nil
}
// createLoader creates a loader service configured for core plugins.
func createLoader(cfg *config.PluginManagementCfg) pluginsLoader.Service {
d := discovery.New(cfg, discovery.Opts{
FilterFuncs: []discovery.FilterFunc{
// Allow all plugin types for core plugins
},
})
b := bootstrap.New(cfg, bootstrap.Opts{
DecorateFuncs: []bootstrap.DecorateFunc{}, // no decoration required for metadata
})
v := validation.New(cfg, validation.Opts{
ValidateFuncs: []validation.ValidateFunc{
// Skip validation for core plugins - they're trusted
},
})
i := initialization.New(cfg, initialization.Opts{
InitializeFuncs: []initialization.InitializeFunc{
// Skip initialization - we only need metadata, not running plugins
},
})
t, _ := termination.New(cfg, termination.Opts{
TerminateFuncs: []termination.TerminateFunc{
// No termination needed for metadata-only loading
},
})
et := pluginerrs.ProvideErrorTracker()
return pluginsLoader.New(cfg, d, b, v, i, t, et)
}
// pluginToMetaSpec converts a fully loaded *plugins.Plugin to a pluginsv0alpha1.MetaSpec.
func (p *CoreProvider) pluginToMetaSpec(plugin *plugins.Plugin) pluginsv0alpha1.MetaSpec {
metaSpec := pluginsv0alpha1.MetaSpec{
PluginJson: jsonDataToMetaJSONData(plugin.JSONData),
}
// Set module information
if plugin.Module != "" {
module := &pluginsv0alpha1.MetaV0alpha1SpecModule{
Path: plugin.Module,
}
loadingStrategy := pluginsv0alpha1.MetaV0alpha1SpecModuleLoadingStrategyScript
module.LoadingStrategy = &loadingStrategy
metaSpec.Module = module
}
// Set BaseURL
if plugin.BaseURL != "" {
metaSpec.BaseURL = &plugin.BaseURL
}
// Set signature information
signature := &pluginsv0alpha1.MetaV0alpha1SpecSignature{
Status: convertSignatureStatus(plugin.Signature),
}
if plugin.SignatureType != "" {
sigType := convertSignatureType(plugin.SignatureType)
signature.Type = &sigType
}
if plugin.SignatureOrg != "" {
signature.Org = &plugin.SignatureOrg
}
metaSpec.Signature = signature
if len(plugin.Children) > 0 {
children := make([]string, 0, len(plugin.Children))
for _, child := range plugin.Children {
children = append(children, child.ID)
}
metaSpec.Children = children
}
metaSpec.Angular = &pluginsv0alpha1.MetaV0alpha1SpecAngular{
Detected: plugin.Angular.Detected,
}
if len(plugin.Translations) > 0 {
metaSpec.Translations = plugin.Translations
}
return metaSpec
}
// jsonDataToMetaJSONData converts a plugins.JSONData to a pluginsv0alpha1.MetaJSONData.
// nolint:gocyclo
func jsonDataToMetaJSONData(jsonData plugins.JSONData) pluginsv0alpha1.MetaJSONData {
+5 -5
View File
@@ -57,11 +57,11 @@ func TestIntegrationPluginMeta(t *testing.T) {
foundIDs := make(map[string]bool)
for _, item := range response.Result.Items {
require.NotNil(t, item.Spec.PluginJSON)
foundIDs[item.Spec.PluginJSON.Id] = true
require.NotEmpty(t, item.Spec.PluginJSON.Id)
require.NotEmpty(t, item.Spec.PluginJSON.Type)
require.NotEmpty(t, item.Spec.PluginJSON.Name)
require.NotNil(t, item.Spec.PluginJson)
foundIDs[item.Spec.PluginJson.Id] = true
require.NotEmpty(t, item.Spec.PluginJson.Id)
require.NotEmpty(t, item.Spec.PluginJson.Type)
require.NotEmpty(t, item.Spec.PluginJson.Name)
}
require.True(t, foundIDs["grafana-piechart-panel"])
require.True(t, foundIDs["grafana-clock-panel"])
@@ -1,85 +0,0 @@
package plugins
import (
"context"
"fmt"
"testing"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1"
"github.com/grafana/grafana/pkg/tests/apis"
"github.com/grafana/grafana/pkg/util/testutil"
)
func TestIntegrationPluginMetas(t *testing.T) {
testutil.SkipIntegrationTestInShortMode(t)
t.Run("list plugin metas", func(t *testing.T) {
helper := setupHelper(t)
ctx := context.Background()
client := helper.GetResourceClient(apis.ResourceClientArgs{
User: helper.Org1.Admin,
GVR: gvrPlugins,
})
plugin1Name := "test-plugin-metas-1"
plugin1 := helper.LoadYAMLOrJSON(fmt.Sprintf(`{
"apiVersion": "plugins.grafana.app/v0alpha1",
"kind": "Plugin",
"metadata": {"name": "%s"},
"spec": {"id": "grafana-piechart-panel", "version": "1.0.0"}
}`, plugin1Name))
_, err := client.Resource.Create(ctx, plugin1, metav1.CreateOptions{})
require.NoError(t, err)
plugin2Name := "test-plugin-metas-2"
plugin2 := helper.LoadYAMLOrJSON(fmt.Sprintf(`{
"apiVersion": "plugins.grafana.app/v0alpha1",
"kind": "Plugin",
"metadata": {"name": "%s"},
"spec": {"id": "grafana-clock-panel", "version": "1.0.0"}
}`, plugin2Name))
_, err = client.Resource.Create(ctx, plugin2, metav1.CreateOptions{})
require.NoError(t, err)
namespace := helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID())
path := fmt.Sprintf("/apis/plugins.grafana.app/v0alpha1/namespaces/%s/pluginmetas", namespace)
response := apis.DoRequest(helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: "GET",
Path: path,
}, &pluginsv0alpha1.PluginMetaList{})
require.NotNil(t, response.Result)
require.NotNil(t, response.Result.Items)
require.GreaterOrEqual(t, len(response.Result.Items), 2)
foundIDs := make(map[string]bool)
for _, item := range response.Result.Items {
require.NotNil(t, item.Spec.PluginJson)
foundIDs[item.Spec.PluginJson.Id] = true
require.NotEmpty(t, item.Spec.PluginJson.Id)
require.NotEmpty(t, item.Spec.PluginJson.Type)
require.NotEmpty(t, item.Spec.PluginJson.Name)
}
require.True(t, foundIDs["grafana-piechart-panel"])
require.True(t, foundIDs["grafana-clock-panel"])
})
t.Run("list plugin metas with no plugins", func(t *testing.T) {
helper := setupHelper(t)
namespace := helper.Namespacer(helper.Org1.Admin.Identity.GetOrgID())
path := fmt.Sprintf("/apis/plugins.grafana.app/v0alpha1/namespaces/%s/pluginmetas", namespace)
response := apis.DoRequest(helper, apis.RequestParams{
User: helper.Org1.Admin,
Method: "GET",
Path: path,
}, &pluginsv0alpha1.PluginMetaList{})
require.NotNil(t, response.Result)
require.NotNil(t, response.Result.Items)
require.GreaterOrEqual(t, len(response.Result.Items), 0)
})
}