flesh out local for demo

This commit is contained in:
Will Browne
2025-11-26 11:28:16 +00:00
parent cef4449f14
commit 881c81f0b3
18 changed files with 982 additions and 583 deletions
+61 -2
View File
@@ -207,13 +207,18 @@ func NewPluginMetaExtensions() *PluginMetaExtensions {
// +k8s:openapi-gen=true
type PluginMetaSpec struct {
PluginJSON PluginMetaJSONData `json:"pluginJSON"`
PluginJson PluginMetaJSONData `json:"pluginJson"`
Module *PluginMetaV0alpha1SpecModule `json:"module,omitempty"`
BaseURL *string `json:"baseURL,omitempty"`
Signature *PluginMetaV0alpha1SpecSignature `json:"signature,omitempty"`
// +listType=atomic
Children []string `json:"children,omitempty"`
}
// NewPluginMetaSpec creates a new PluginMetaSpec object.
func NewPluginMetaSpec() *PluginMetaSpec {
return &PluginMetaSpec{
PluginJSON: *NewPluginMetaJSONData(),
PluginJson: *NewPluginMetaJSONData(),
}
}
@@ -411,6 +416,30 @@ func NewPluginMetaV0alpha1ExtensionsExtensionPoints() *PluginMetaV0alpha1Extensi
return &PluginMetaV0alpha1ExtensionsExtensionPoints{}
}
// +k8s:openapi-gen=true
type PluginMetaV0alpha1SpecModule struct {
Path string `json:"path"`
Hash *string `json:"hash,omitempty"`
LoadingStrategy *PluginMetaV0alpha1SpecModuleLoadingStrategy `json:"loadingStrategy,omitempty"`
}
// NewPluginMetaV0alpha1SpecModule creates a new PluginMetaV0alpha1SpecModule object.
func NewPluginMetaV0alpha1SpecModule() *PluginMetaV0alpha1SpecModule {
return &PluginMetaV0alpha1SpecModule{}
}
// +k8s:openapi-gen=true
type PluginMetaV0alpha1SpecSignature struct {
Status PluginMetaV0alpha1SpecSignatureStatus `json:"status"`
Type *PluginMetaV0alpha1SpecSignatureType `json:"type,omitempty"`
Org *string `json:"org,omitempty"`
}
// NewPluginMetaV0alpha1SpecSignature creates a new PluginMetaV0alpha1SpecSignature object.
func NewPluginMetaV0alpha1SpecSignature() *PluginMetaV0alpha1SpecSignature {
return &PluginMetaV0alpha1SpecSignature{}
}
// +k8s:openapi-gen=true
type PluginMetaJSONDataType string
@@ -471,3 +500,33 @@ const (
PluginMetaV0alpha1DependenciesPluginsTypeDatasource PluginMetaV0alpha1DependenciesPluginsType = "datasource"
PluginMetaV0alpha1DependenciesPluginsTypePanel PluginMetaV0alpha1DependenciesPluginsType = "panel"
)
// +k8s:openapi-gen=true
type PluginMetaV0alpha1SpecModuleLoadingStrategy string
const (
PluginMetaV0alpha1SpecModuleLoadingStrategyFetch PluginMetaV0alpha1SpecModuleLoadingStrategy = "fetch"
PluginMetaV0alpha1SpecModuleLoadingStrategyScript PluginMetaV0alpha1SpecModuleLoadingStrategy = "script"
)
// +k8s:openapi-gen=true
type PluginMetaV0alpha1SpecSignatureStatus string
const (
PluginMetaV0alpha1SpecSignatureStatusInternal PluginMetaV0alpha1SpecSignatureStatus = "internal"
PluginMetaV0alpha1SpecSignatureStatusValid PluginMetaV0alpha1SpecSignatureStatus = "valid"
PluginMetaV0alpha1SpecSignatureStatusInvalid PluginMetaV0alpha1SpecSignatureStatus = "invalid"
PluginMetaV0alpha1SpecSignatureStatusModified PluginMetaV0alpha1SpecSignatureStatus = "modified"
PluginMetaV0alpha1SpecSignatureStatusUnsigned PluginMetaV0alpha1SpecSignatureStatus = "unsigned"
)
// +k8s:openapi-gen=true
type PluginMetaV0alpha1SpecSignatureType string
const (
PluginMetaV0alpha1SpecSignatureTypeGrafana PluginMetaV0alpha1SpecSignatureType = "grafana"
PluginMetaV0alpha1SpecSignatureTypeCommercial PluginMetaV0alpha1SpecSignatureType = "commercial"
PluginMetaV0alpha1SpecSignatureTypeCommunity PluginMetaV0alpha1SpecSignatureType = "community"
PluginMetaV0alpha1SpecSignatureTypePrivate PluginMetaV0alpha1SpecSignatureType = "private"
PluginMetaV0alpha1SpecSignatureTypePrivateGlob PluginMetaV0alpha1SpecSignatureType = "private-glob"
)
File diff suppressed because one or more lines are too long
+54 -20
View File
@@ -87,8 +87,42 @@ func (p *CloudProvider) GetMeta(ctx context.Context, pluginID, version string) (
return nil, fmt.Errorf("failed to decode response: %w", err)
}
spec := pluginsv0alpha1.PluginMetaSpec{
PluginJson: gcomMeta.JSON,
}
if gcomMeta.VersionSignatureType != "" {
signature := &pluginsv0alpha1.PluginMetaV0alpha1SpecSignature{
Status: pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatusValid,
}
switch gcomMeta.VersionSignatureType {
case "grafana":
sigType := pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypeGrafana
signature.Type = &sigType
case "commercial":
sigType := pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypeCommercial
signature.Type = &sigType
case "community":
sigType := pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypeCommunity
signature.Type = &sigType
case "private":
sigType := pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypePrivate
signature.Type = &sigType
case "private-glob":
sigType := pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypePrivateGlob
signature.Type = &sigType
}
if gcomMeta.VersionSignedByOrg != "" {
signature.Org = &gcomMeta.VersionSignedByOrg
}
spec.Signature = signature
}
return &Result{
Meta: gcomMeta.JSON,
Meta: spec,
TTL: p.ttl,
}, nil
}
@@ -96,25 +130,25 @@ func (p *CloudProvider) GetMeta(ctx context.Context, pluginID, version string) (
// grafanaComPluginVersionMeta represents the response from grafana.com API
// GET /api/plugins/{pluginId}/versions/{version}
type grafanaComPluginVersionMeta struct {
PluginID string `json:"pluginSlug"`
Version string `json:"version"`
URL string `json:"url"`
Commit string `json:"commit"`
Description string `json:"description"`
Keywords []string `json:"keywords"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
JSON pluginsv0alpha1.PluginMetaJSONData `json:"json"`
Readme string `json:"readme"`
Downloads int `json:"downloads"`
Verified bool `json:"verified"`
Status string `json:"status"`
StatusContext string `json:"statusContext"`
DownloadSlug string `json:"downloadSlug"`
SignatureType string `json:"signatureType"`
SignedByOrg string `json:"signedByOrg"`
SignedByOrgName string `json:"signedByOrgName"`
Packages struct {
PluginID string `json:"pluginSlug"`
Version string `json:"version"`
URL string `json:"url"`
Commit string `json:"commit"`
Description string `json:"description"`
Keywords []string `json:"keywords"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
JSON pluginsv0alpha1.PluginMetaJSONData `json:"json"`
Readme string `json:"readme"`
Downloads int `json:"downloads"`
Verified bool `json:"verified"`
Status string `json:"status"`
StatusContext string `json:"statusContext"`
DownloadSlug string `json:"downloadSlug"`
VersionSignatureType string `json:"versionSignatureType"`
VersionSignedByOrg string `json:"versionSignedByOrg"`
VersionSignedByOrgName string `json:"versionSignedByOrgName"`
Packages struct {
Any struct {
Md5 string `json:"md5"`
Sha256 string `json:"sha256"`
+1 -1
View File
@@ -49,7 +49,7 @@ func TestCloudProvider_GetMeta(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, expectedMeta, result.Meta)
assert.Equal(t, expectedMeta, result.Meta.PluginJSON)
assert.Equal(t, defaultCloudTTL, result.TTL)
})
+627
View File
@@ -0,0 +1,627 @@
package meta
import (
"encoding/json"
pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
)
// jsonDataToPluginMetaJSONData converts a plugins.JSONData to a pluginsv0alpha1.PluginMetaJSONData.
// nolint:gocyclo
func jsonDataToPluginMetaJSONData(jsonData plugins.JSONData) pluginsv0alpha1.PluginMetaJSONData {
meta := pluginsv0alpha1.PluginMetaJSONData{
Id: jsonData.ID,
Name: jsonData.Name,
}
// Map plugin type
switch jsonData.Type {
case plugins.TypeApp:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeApp
case plugins.TypeDataSource:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeDatasource
case plugins.TypePanel:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypePanel
case plugins.TypeRenderer:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeRenderer
}
// Map Info
meta.Info = pluginsv0alpha1.PluginMetaInfo{
Keywords: jsonData.Info.Keywords,
Logos: pluginsv0alpha1.PluginMetaV0alpha1InfoLogos{
Small: jsonData.Info.Logos.Small,
Large: jsonData.Info.Logos.Large,
},
Updated: jsonData.Info.Updated,
Version: jsonData.Info.Version,
}
if jsonData.Info.Description != "" {
meta.Info.Description = &jsonData.Info.Description
}
if jsonData.Info.Author.Name != "" || jsonData.Info.Author.URL != "" {
author := &pluginsv0alpha1.PluginMetaV0alpha1InfoAuthor{}
if jsonData.Info.Author.Name != "" {
author.Name = &jsonData.Info.Author.Name
}
if jsonData.Info.Author.URL != "" {
author.Url = &jsonData.Info.Author.URL
}
meta.Info.Author = author
}
if len(jsonData.Info.Links) > 0 {
meta.Info.Links = make([]pluginsv0alpha1.PluginMetaV0alpha1InfoLinks, 0, len(jsonData.Info.Links))
for _, link := range jsonData.Info.Links {
v0Link := pluginsv0alpha1.PluginMetaV0alpha1InfoLinks{}
if link.Name != "" {
v0Link.Name = &link.Name
}
if link.URL != "" {
v0Link.Url = &link.URL
}
meta.Info.Links = append(meta.Info.Links, v0Link)
}
}
if len(jsonData.Info.Screenshots) > 0 {
meta.Info.Screenshots = make([]pluginsv0alpha1.PluginMetaV0alpha1InfoScreenshots, 0, len(jsonData.Info.Screenshots))
for _, screenshot := range jsonData.Info.Screenshots {
v0Screenshot := pluginsv0alpha1.PluginMetaV0alpha1InfoScreenshots{}
if screenshot.Name != "" {
v0Screenshot.Name = &screenshot.Name
}
if screenshot.Path != "" {
v0Screenshot.Path = &screenshot.Path
}
meta.Info.Screenshots = append(meta.Info.Screenshots, v0Screenshot)
}
}
// Map Dependencies
meta.Dependencies = pluginsv0alpha1.PluginMetaDependencies{
GrafanaDependency: jsonData.Dependencies.GrafanaDependency,
}
if jsonData.Dependencies.GrafanaVersion != "" {
meta.Dependencies.GrafanaVersion = &jsonData.Dependencies.GrafanaVersion
}
if len(jsonData.Dependencies.Plugins) > 0 {
meta.Dependencies.Plugins = make([]pluginsv0alpha1.PluginMetaV0alpha1DependenciesPlugins, 0, len(jsonData.Dependencies.Plugins))
for _, dep := range jsonData.Dependencies.Plugins {
var depType pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsType
switch dep.Type {
case "app":
depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypeApp
case "datasource":
depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypeDatasource
case "panel":
depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypePanel
}
meta.Dependencies.Plugins = append(meta.Dependencies.Plugins, pluginsv0alpha1.PluginMetaV0alpha1DependenciesPlugins{
Id: dep.ID,
Type: depType,
Name: dep.Name,
})
}
}
if len(jsonData.Dependencies.Extensions.ExposedComponents) > 0 {
meta.Dependencies.Extensions = &pluginsv0alpha1.PluginMetaV0alpha1DependenciesExtensions{
ExposedComponents: jsonData.Dependencies.Extensions.ExposedComponents,
}
}
// Map optional boolean fields
if jsonData.Alerting {
meta.Alerting = &jsonData.Alerting
}
if jsonData.Annotations {
meta.Annotations = &jsonData.Annotations
}
if jsonData.AutoEnabled {
meta.AutoEnabled = &jsonData.AutoEnabled
}
if jsonData.Backend {
meta.Backend = &jsonData.Backend
}
if jsonData.BuiltIn {
meta.BuiltIn = &jsonData.BuiltIn
}
if jsonData.HideFromList {
meta.HideFromList = &jsonData.HideFromList
}
if jsonData.Logs {
meta.Logs = &jsonData.Logs
}
if jsonData.Metrics {
meta.Metrics = &jsonData.Metrics
}
if jsonData.MultiValueFilterOperators {
meta.MultiValueFilterOperators = &jsonData.MultiValueFilterOperators
}
if jsonData.Preload {
meta.Preload = &jsonData.Preload
}
if jsonData.SkipDataQuery {
meta.SkipDataQuery = &jsonData.SkipDataQuery
}
if jsonData.Streaming {
meta.Streaming = &jsonData.Streaming
}
if jsonData.Tracing {
meta.Tracing = &jsonData.Tracing
}
// Map category
if jsonData.Category != "" {
var category pluginsv0alpha1.PluginMetaJSONDataCategory
switch jsonData.Category {
case "tsdb":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryTsdb
case "logging":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryLogging
case "cloud":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryCloud
case "tracing":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryTracing
case "profiling":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryProfiling
case "sql":
category = pluginsv0alpha1.PluginMetaJSONDataCategorySql
case "enterprise":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryEnterprise
case "iot":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryIot
case "other":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryOther
default:
category = pluginsv0alpha1.PluginMetaJSONDataCategoryOther
}
meta.Category = &category
}
// Map state
if jsonData.State != "" {
var state pluginsv0alpha1.PluginMetaJSONDataState
switch jsonData.State {
case plugins.ReleaseStateAlpha:
state = pluginsv0alpha1.PluginMetaJSONDataStateAlpha
case plugins.ReleaseStateBeta:
state = pluginsv0alpha1.PluginMetaJSONDataStateBeta
default:
}
if state != "" {
meta.State = &state
}
}
// Map executable
if jsonData.Executable != "" {
meta.Executable = &jsonData.Executable
}
// Map QueryOptions
if len(jsonData.QueryOptions) > 0 {
queryOptions := &pluginsv0alpha1.PluginMetaQueryOptions{}
if val, ok := jsonData.QueryOptions["maxDataPoints"]; ok {
queryOptions.MaxDataPoints = &val
}
if val, ok := jsonData.QueryOptions["minInterval"]; ok {
queryOptions.MinInterval = &val
}
if val, ok := jsonData.QueryOptions["cacheTimeout"]; ok {
queryOptions.CacheTimeout = &val
}
meta.QueryOptions = queryOptions
}
// Map Includes
if len(jsonData.Includes) > 0 {
meta.Includes = make([]pluginsv0alpha1.PluginMetaInclude, 0, len(jsonData.Includes))
for _, include := range jsonData.Includes {
v0Include := pluginsv0alpha1.PluginMetaInclude{}
if include.UID != "" {
v0Include.Uid = &include.UID
}
if include.Type != "" {
var includeType pluginsv0alpha1.PluginMetaIncludeType
switch include.Type {
case "dashboard":
includeType = pluginsv0alpha1.PluginMetaIncludeTypeDashboard
case "page":
includeType = pluginsv0alpha1.PluginMetaIncludeTypePage
case "panel":
includeType = pluginsv0alpha1.PluginMetaIncludeTypePanel
case "datasource":
includeType = pluginsv0alpha1.PluginMetaIncludeTypeDatasource
}
v0Include.Type = &includeType
}
if include.Name != "" {
v0Include.Name = &include.Name
}
if include.Component != "" {
v0Include.Component = &include.Component
}
if include.Role != "" {
var role pluginsv0alpha1.PluginMetaIncludeRole
switch include.Role {
case "Admin":
role = pluginsv0alpha1.PluginMetaIncludeRoleAdmin
case "Editor":
role = pluginsv0alpha1.PluginMetaIncludeRoleEditor
case "Viewer":
role = pluginsv0alpha1.PluginMetaIncludeRoleViewer
}
v0Include.Role = &role
}
if include.Action != "" {
v0Include.Action = &include.Action
}
if include.Path != "" {
v0Include.Path = &include.Path
}
if include.AddToNav {
v0Include.AddToNav = &include.AddToNav
}
if include.DefaultNav {
v0Include.DefaultNav = &include.DefaultNav
}
if include.Icon != "" {
v0Include.Icon = &include.Icon
}
meta.Includes = append(meta.Includes, v0Include)
}
}
// Map Routes
if len(jsonData.Routes) > 0 {
meta.Routes = make([]pluginsv0alpha1.PluginMetaRoute, 0, len(jsonData.Routes))
for _, route := range jsonData.Routes {
v0Route := pluginsv0alpha1.PluginMetaRoute{}
if route.Path != "" {
v0Route.Path = &route.Path
}
if route.Method != "" {
v0Route.Method = &route.Method
}
if route.URL != "" {
v0Route.Url = &route.URL
}
if route.ReqRole != "" {
reqRole := string(route.ReqRole)
v0Route.ReqRole = &reqRole
}
if route.ReqAction != "" {
v0Route.ReqAction = &route.ReqAction
}
if len(route.Headers) > 0 {
headers := make([]string, 0, len(route.Headers))
for _, header := range route.Headers {
headers = append(headers, header.Name+": "+header.Content)
}
v0Route.Headers = headers
}
if len(route.URLParams) > 0 {
v0Route.UrlParams = make([]pluginsv0alpha1.PluginMetaV0alpha1RouteUrlParams, 0, len(route.URLParams))
for _, param := range route.URLParams {
v0Param := pluginsv0alpha1.PluginMetaV0alpha1RouteUrlParams{}
if param.Name != "" {
v0Param.Name = &param.Name
}
if param.Content != "" {
v0Param.Content = &param.Content
}
v0Route.UrlParams = append(v0Route.UrlParams, v0Param)
}
}
if route.TokenAuth != nil {
v0Route.TokenAuth = &pluginsv0alpha1.PluginMetaV0alpha1RouteTokenAuth{}
if route.TokenAuth.Url != "" {
v0Route.TokenAuth.Url = &route.TokenAuth.Url
}
if len(route.TokenAuth.Scopes) > 0 {
v0Route.TokenAuth.Scopes = route.TokenAuth.Scopes
}
if len(route.TokenAuth.Params) > 0 {
v0Route.TokenAuth.Params = make(map[string]interface{})
for k, v := range route.TokenAuth.Params {
v0Route.TokenAuth.Params[k] = v
}
}
}
if route.JwtTokenAuth != nil {
v0Route.JwtTokenAuth = &pluginsv0alpha1.PluginMetaV0alpha1RouteJwtTokenAuth{}
if route.JwtTokenAuth.Url != "" {
v0Route.JwtTokenAuth.Url = &route.JwtTokenAuth.Url
}
if len(route.JwtTokenAuth.Scopes) > 0 {
v0Route.JwtTokenAuth.Scopes = route.JwtTokenAuth.Scopes
}
if len(route.JwtTokenAuth.Params) > 0 {
v0Route.JwtTokenAuth.Params = make(map[string]interface{})
for k, v := range route.JwtTokenAuth.Params {
v0Route.JwtTokenAuth.Params[k] = v
}
}
}
if len(route.Body) > 0 {
var bodyMap map[string]interface{}
if err := json.Unmarshal(route.Body, &bodyMap); err == nil {
v0Route.Body = bodyMap
}
}
meta.Routes = append(meta.Routes, v0Route)
}
}
// Map Extensions
if len(jsonData.Extensions.AddedLinks) > 0 || len(jsonData.Extensions.AddedComponents) > 0 ||
len(jsonData.Extensions.ExposedComponents) > 0 || len(jsonData.Extensions.ExtensionPoints) > 0 {
extensions := &pluginsv0alpha1.PluginMetaExtensions{}
if len(jsonData.Extensions.AddedLinks) > 0 {
extensions.AddedLinks = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedLinks, 0, len(jsonData.Extensions.AddedLinks))
for _, link := range jsonData.Extensions.AddedLinks {
v0Link := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedLinks{
Targets: link.Targets,
Title: link.Title,
}
if link.Description != "" {
v0Link.Description = &link.Description
}
extensions.AddedLinks = append(extensions.AddedLinks, v0Link)
}
}
if len(jsonData.Extensions.AddedComponents) > 0 {
extensions.AddedComponents = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedComponents, 0, len(jsonData.Extensions.AddedComponents))
for _, comp := range jsonData.Extensions.AddedComponents {
v0Comp := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedComponents{
Targets: comp.Targets,
Title: comp.Title,
}
if comp.Description != "" {
v0Comp.Description = &comp.Description
}
extensions.AddedComponents = append(extensions.AddedComponents, v0Comp)
}
}
if len(jsonData.Extensions.ExposedComponents) > 0 {
extensions.ExposedComponents = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExposedComponents, 0, len(jsonData.Extensions.ExposedComponents))
for _, comp := range jsonData.Extensions.ExposedComponents {
v0Comp := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExposedComponents{
Id: comp.Id,
}
if comp.Title != "" {
v0Comp.Title = &comp.Title
}
if comp.Description != "" {
v0Comp.Description = &comp.Description
}
extensions.ExposedComponents = append(extensions.ExposedComponents, v0Comp)
}
}
if len(jsonData.Extensions.ExtensionPoints) > 0 {
extensions.ExtensionPoints = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExtensionPoints, 0, len(jsonData.Extensions.ExtensionPoints))
for _, point := range jsonData.Extensions.ExtensionPoints {
v0Point := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExtensionPoints{
Id: point.Id,
}
if point.Title != "" {
v0Point.Title = &point.Title
}
if point.Description != "" {
v0Point.Description = &point.Description
}
extensions.ExtensionPoints = append(extensions.ExtensionPoints, v0Point)
}
}
meta.Extensions = extensions
}
// Map Roles
if len(jsonData.Roles) > 0 {
meta.Roles = make([]pluginsv0alpha1.PluginMetaRole, 0, len(jsonData.Roles))
for _, role := range jsonData.Roles {
v0Role := pluginsv0alpha1.PluginMetaRole{
Grants: role.Grants,
}
if role.Role.Name != "" || role.Role.Description != "" || len(role.Role.Permissions) > 0 {
v0RoleRole := &pluginsv0alpha1.PluginMetaV0alpha1RoleRole{}
if role.Role.Name != "" {
v0RoleRole.Name = &role.Role.Name
}
if role.Role.Description != "" {
v0RoleRole.Description = &role.Role.Description
}
if len(role.Role.Permissions) > 0 {
v0RoleRole.Permissions = make([]pluginsv0alpha1.PluginMetaV0alpha1RoleRolePermissions, 0, len(role.Role.Permissions))
for _, perm := range role.Role.Permissions {
v0Perm := pluginsv0alpha1.PluginMetaV0alpha1RoleRolePermissions{}
if perm.Action != "" {
v0Perm.Action = &perm.Action
}
if perm.Scope != "" {
v0Perm.Scope = &perm.Scope
}
v0RoleRole.Permissions = append(v0RoleRole.Permissions, v0Perm)
}
}
v0Role.Role = v0RoleRole
}
meta.Roles = append(meta.Roles, v0Role)
}
}
// Map IAM
if jsonData.IAM != nil && len(jsonData.IAM.Permissions) > 0 {
iam := &pluginsv0alpha1.PluginMetaIAM{
Permissions: make([]pluginsv0alpha1.PluginMetaV0alpha1IAMPermissions, 0, len(jsonData.IAM.Permissions)),
}
for _, perm := range jsonData.IAM.Permissions {
v0Perm := pluginsv0alpha1.PluginMetaV0alpha1IAMPermissions{}
if perm.Action != "" {
v0Perm.Action = &perm.Action
}
if perm.Scope != "" {
v0Perm.Scope = &perm.Scope
}
iam.Permissions = append(iam.Permissions, v0Perm)
}
meta.Iam = iam
}
return meta
}
// pluginToPluginMetaSpec converts a plugins.Plugin to a pluginsv0alpha1.PluginMetaSpec.
// This includes fields that are only available on the full Plugin struct (not just JSONData).
func pluginToPluginMetaSpec(plugin *plugins.Plugin) pluginsv0alpha1.PluginMetaSpec {
spec := pluginsv0alpha1.PluginMetaSpec{
PluginJson: jsonDataToPluginMetaJSONData(plugin.JSONData),
}
if plugin.Module != "" {
module := &pluginsv0alpha1.PluginMetaV0alpha1SpecModule{
Path: plugin.Module,
}
spec.Module = module
}
if plugin.BaseURL != "" {
spec.BaseURL = &plugin.BaseURL
}
if plugin.Signature != "" {
signature := &pluginsv0alpha1.PluginMetaV0alpha1SpecSignature{
Status: convertSignatureStatus(plugin.Signature),
}
if plugin.SignatureType != "" {
sigType := convertSignatureType(plugin.SignatureType)
signature.Type = &sigType
}
if plugin.SignatureOrg != "" {
signature.Org = &plugin.SignatureOrg
}
spec.Signature = signature
}
if len(plugin.Children) > 0 {
children := make([]string, 0, len(plugin.Children))
for _, child := range plugin.Children {
if child != nil {
children = append(children, child.ID)
}
}
if len(children) > 0 {
spec.Children = children
}
}
return spec
}
// pluginStorePluginToPluginMetaSpec converts a pluginstore.Plugin to a pluginsv0alpha1.PluginMetaSpec.
// This is similar to pluginToPluginMetaSpec but works with the plugin store DTO.
// loadingStrategy and moduleHash are optional calculated values that can be provided.
func pluginStorePluginToPluginMetaSpec(plugin pluginstore.Plugin, loadingStrategy plugins.LoadingStrategy, moduleHash string) pluginsv0alpha1.PluginMetaSpec {
spec := pluginsv0alpha1.PluginMetaSpec{
PluginJson: jsonDataToPluginMetaJSONData(plugin.JSONData),
}
if plugin.Module != "" {
module := &pluginsv0alpha1.PluginMetaV0alpha1SpecModule{
Path: plugin.Module,
}
if moduleHash != "" {
module.Hash = &moduleHash
}
if loadingStrategy != "" {
var ls pluginsv0alpha1.PluginMetaV0alpha1SpecModuleLoadingStrategy
switch loadingStrategy {
case plugins.LoadingStrategyFetch:
ls = pluginsv0alpha1.PluginMetaV0alpha1SpecModuleLoadingStrategyFetch
case plugins.LoadingStrategyScript:
ls = pluginsv0alpha1.PluginMetaV0alpha1SpecModuleLoadingStrategyScript
}
module.LoadingStrategy = &ls
}
spec.Module = module
}
if plugin.BaseURL != "" {
spec.BaseURL = &plugin.BaseURL
}
if plugin.Signature != "" {
signature := &pluginsv0alpha1.PluginMetaV0alpha1SpecSignature{
Status: convertSignatureStatus(plugin.Signature),
}
if plugin.SignatureType != "" {
sigType := convertSignatureType(plugin.SignatureType)
signature.Type = &sigType
}
if plugin.SignatureOrg != "" {
signature.Org = &plugin.SignatureOrg
}
spec.Signature = signature
}
if len(plugin.Children) > 0 {
spec.Children = plugin.Children
}
return spec
}
// convertSignatureStatus converts plugins.SignatureStatus to pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatus.
func convertSignatureStatus(status plugins.SignatureStatus) pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatus {
switch status {
case plugins.SignatureStatusInternal:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatusInternal
case plugins.SignatureStatusValid:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatusValid
case plugins.SignatureStatusInvalid:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatusInvalid
case plugins.SignatureStatusModified:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatusModified
case plugins.SignatureStatusUnsigned:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatusUnsigned
default:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureStatusUnsigned
}
}
// convertSignatureType converts plugins.SignatureType to pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureType.
func convertSignatureType(sigType plugins.SignatureType) pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureType {
switch sigType {
case plugins.SignatureTypeGrafana:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypeGrafana
case plugins.SignatureTypeCommercial:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypeCommercial
case plugins.SignatureTypeCommunity:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypeCommunity
case plugins.SignatureTypePrivate:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypePrivate
case plugins.SignatureTypePrivateGlob:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypePrivateGlob
default:
return pluginsv0alpha1.PluginMetaV0alpha1SpecSignatureTypeGrafana
}
}
+8 -483
View File
@@ -2,7 +2,6 @@ package meta
import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
@@ -23,7 +22,7 @@ const (
// CoreProvider retrieves plugin metadata for core plugins.
type CoreProvider struct {
mu sync.RWMutex
loadedPlugins map[string]pluginsv0alpha1.PluginMetaJSONData
loadedPlugins map[string]pluginsv0alpha1.PluginMetaSpec
initialized bool
ttl time.Duration
}
@@ -36,7 +35,7 @@ func NewCoreProvider() *CoreProvider {
// NewCoreProviderWithTTL creates a new CoreProvider with a custom TTL.
func NewCoreProviderWithTTL(ttl time.Duration) *CoreProvider {
return &CoreProvider{
loadedPlugins: make(map[string]pluginsv0alpha1.PluginMetaJSONData),
loadedPlugins: make(map[string]pluginsv0alpha1.PluginMetaSpec),
ttl: ttl,
}
}
@@ -76,9 +75,9 @@ func (p *CoreProvider) GetMeta(ctx context.Context, pluginID, _ string) (*Result
p.initialized = true
}
if meta, found := p.loadedPlugins[pluginID]; found {
if spec, found := p.loadedPlugins[pluginID]; found {
return &Result{
Meta: meta,
Meta: spec,
TTL: p.ttl,
}, nil
}
@@ -119,485 +118,11 @@ func (p *CoreProvider) loadPlugins(ctx context.Context) error {
}
for _, bundle := range ps {
meta := jsonDataToPluginMetaJSONData(bundle.Primary.JSONData)
p.loadedPlugins[bundle.Primary.JSONData.ID] = meta
spec := pluginsv0alpha1.PluginMetaSpec{
PluginJson: jsonDataToPluginMetaJSONData(bundle.Primary.JSONData),
}
p.loadedPlugins[bundle.Primary.JSONData.ID] = spec
}
return nil
}
// jsonDataToPluginMetaJSONData converts a plugins.JSONData to a pluginsv0alpha1.PluginMetaJSONData.
// nolint:gocyclo
func jsonDataToPluginMetaJSONData(jsonData plugins.JSONData) pluginsv0alpha1.PluginMetaJSONData {
meta := pluginsv0alpha1.PluginMetaJSONData{
Id: jsonData.ID,
Name: jsonData.Name,
}
// Map plugin type
switch jsonData.Type {
case plugins.TypeApp:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeApp
case plugins.TypeDataSource:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeDatasource
case plugins.TypePanel:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypePanel
case plugins.TypeRenderer:
meta.Type = pluginsv0alpha1.PluginMetaJSONDataTypeRenderer
}
// Map Info
meta.Info = pluginsv0alpha1.PluginMetaInfo{
Keywords: jsonData.Info.Keywords,
Logos: pluginsv0alpha1.PluginMetaV0alpha1InfoLogos{
Small: jsonData.Info.Logos.Small,
Large: jsonData.Info.Logos.Large,
},
Updated: jsonData.Info.Updated,
Version: jsonData.Info.Version,
}
if jsonData.Info.Description != "" {
meta.Info.Description = &jsonData.Info.Description
}
if jsonData.Info.Author.Name != "" || jsonData.Info.Author.URL != "" {
author := &pluginsv0alpha1.PluginMetaV0alpha1InfoAuthor{}
if jsonData.Info.Author.Name != "" {
author.Name = &jsonData.Info.Author.Name
}
if jsonData.Info.Author.URL != "" {
author.Url = &jsonData.Info.Author.URL
}
meta.Info.Author = author
}
if len(jsonData.Info.Links) > 0 {
meta.Info.Links = make([]pluginsv0alpha1.PluginMetaV0alpha1InfoLinks, 0, len(jsonData.Info.Links))
for _, link := range jsonData.Info.Links {
v0Link := pluginsv0alpha1.PluginMetaV0alpha1InfoLinks{}
if link.Name != "" {
v0Link.Name = &link.Name
}
if link.URL != "" {
v0Link.Url = &link.URL
}
meta.Info.Links = append(meta.Info.Links, v0Link)
}
}
if len(jsonData.Info.Screenshots) > 0 {
meta.Info.Screenshots = make([]pluginsv0alpha1.PluginMetaV0alpha1InfoScreenshots, 0, len(jsonData.Info.Screenshots))
for _, screenshot := range jsonData.Info.Screenshots {
v0Screenshot := pluginsv0alpha1.PluginMetaV0alpha1InfoScreenshots{}
if screenshot.Name != "" {
v0Screenshot.Name = &screenshot.Name
}
if screenshot.Path != "" {
v0Screenshot.Path = &screenshot.Path
}
meta.Info.Screenshots = append(meta.Info.Screenshots, v0Screenshot)
}
}
// Map Dependencies
meta.Dependencies = pluginsv0alpha1.PluginMetaDependencies{
GrafanaDependency: jsonData.Dependencies.GrafanaDependency,
}
if jsonData.Dependencies.GrafanaVersion != "" {
meta.Dependencies.GrafanaVersion = &jsonData.Dependencies.GrafanaVersion
}
if len(jsonData.Dependencies.Plugins) > 0 {
meta.Dependencies.Plugins = make([]pluginsv0alpha1.PluginMetaV0alpha1DependenciesPlugins, 0, len(jsonData.Dependencies.Plugins))
for _, dep := range jsonData.Dependencies.Plugins {
var depType pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsType
switch dep.Type {
case "app":
depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypeApp
case "datasource":
depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypeDatasource
case "panel":
depType = pluginsv0alpha1.PluginMetaV0alpha1DependenciesPluginsTypePanel
}
meta.Dependencies.Plugins = append(meta.Dependencies.Plugins, pluginsv0alpha1.PluginMetaV0alpha1DependenciesPlugins{
Id: dep.ID,
Type: depType,
Name: dep.Name,
})
}
}
if len(jsonData.Dependencies.Extensions.ExposedComponents) > 0 {
meta.Dependencies.Extensions = &pluginsv0alpha1.PluginMetaV0alpha1DependenciesExtensions{
ExposedComponents: jsonData.Dependencies.Extensions.ExposedComponents,
}
}
// Map optional boolean fields
if jsonData.Alerting {
meta.Alerting = &jsonData.Alerting
}
if jsonData.Annotations {
meta.Annotations = &jsonData.Annotations
}
if jsonData.AutoEnabled {
meta.AutoEnabled = &jsonData.AutoEnabled
}
if jsonData.Backend {
meta.Backend = &jsonData.Backend
}
if jsonData.BuiltIn {
meta.BuiltIn = &jsonData.BuiltIn
}
if jsonData.HideFromList {
meta.HideFromList = &jsonData.HideFromList
}
if jsonData.Logs {
meta.Logs = &jsonData.Logs
}
if jsonData.Metrics {
meta.Metrics = &jsonData.Metrics
}
if jsonData.MultiValueFilterOperators {
meta.MultiValueFilterOperators = &jsonData.MultiValueFilterOperators
}
if jsonData.Preload {
meta.Preload = &jsonData.Preload
}
if jsonData.SkipDataQuery {
meta.SkipDataQuery = &jsonData.SkipDataQuery
}
if jsonData.Streaming {
meta.Streaming = &jsonData.Streaming
}
if jsonData.Tracing {
meta.Tracing = &jsonData.Tracing
}
// Map category
if jsonData.Category != "" {
var category pluginsv0alpha1.PluginMetaJSONDataCategory
switch jsonData.Category {
case "tsdb":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryTsdb
case "logging":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryLogging
case "cloud":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryCloud
case "tracing":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryTracing
case "profiling":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryProfiling
case "sql":
category = pluginsv0alpha1.PluginMetaJSONDataCategorySql
case "enterprise":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryEnterprise
case "iot":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryIot
case "other":
category = pluginsv0alpha1.PluginMetaJSONDataCategoryOther
default:
category = pluginsv0alpha1.PluginMetaJSONDataCategoryOther
}
meta.Category = &category
}
// Map state
if jsonData.State != "" {
var state pluginsv0alpha1.PluginMetaJSONDataState
switch jsonData.State {
case plugins.ReleaseStateAlpha:
state = pluginsv0alpha1.PluginMetaJSONDataStateAlpha
case plugins.ReleaseStateBeta:
state = pluginsv0alpha1.PluginMetaJSONDataStateBeta
default:
}
if state != "" {
meta.State = &state
}
}
// Map executable
if jsonData.Executable != "" {
meta.Executable = &jsonData.Executable
}
// Map QueryOptions
if len(jsonData.QueryOptions) > 0 {
queryOptions := &pluginsv0alpha1.PluginMetaQueryOptions{}
if val, ok := jsonData.QueryOptions["maxDataPoints"]; ok {
queryOptions.MaxDataPoints = &val
}
if val, ok := jsonData.QueryOptions["minInterval"]; ok {
queryOptions.MinInterval = &val
}
if val, ok := jsonData.QueryOptions["cacheTimeout"]; ok {
queryOptions.CacheTimeout = &val
}
meta.QueryOptions = queryOptions
}
// Map Includes
if len(jsonData.Includes) > 0 {
meta.Includes = make([]pluginsv0alpha1.PluginMetaInclude, 0, len(jsonData.Includes))
for _, include := range jsonData.Includes {
v0Include := pluginsv0alpha1.PluginMetaInclude{}
if include.UID != "" {
v0Include.Uid = &include.UID
}
if include.Type != "" {
var includeType pluginsv0alpha1.PluginMetaIncludeType
switch include.Type {
case "dashboard":
includeType = pluginsv0alpha1.PluginMetaIncludeTypeDashboard
case "page":
includeType = pluginsv0alpha1.PluginMetaIncludeTypePage
case "panel":
includeType = pluginsv0alpha1.PluginMetaIncludeTypePanel
case "datasource":
includeType = pluginsv0alpha1.PluginMetaIncludeTypeDatasource
}
v0Include.Type = &includeType
}
if include.Name != "" {
v0Include.Name = &include.Name
}
if include.Component != "" {
v0Include.Component = &include.Component
}
if include.Role != "" {
var role pluginsv0alpha1.PluginMetaIncludeRole
switch include.Role {
case "Admin":
role = pluginsv0alpha1.PluginMetaIncludeRoleAdmin
case "Editor":
role = pluginsv0alpha1.PluginMetaIncludeRoleEditor
case "Viewer":
role = pluginsv0alpha1.PluginMetaIncludeRoleViewer
}
v0Include.Role = &role
}
if include.Action != "" {
v0Include.Action = &include.Action
}
if include.Path != "" {
v0Include.Path = &include.Path
}
if include.AddToNav {
v0Include.AddToNav = &include.AddToNav
}
if include.DefaultNav {
v0Include.DefaultNav = &include.DefaultNav
}
if include.Icon != "" {
v0Include.Icon = &include.Icon
}
meta.Includes = append(meta.Includes, v0Include)
}
}
// Map Routes
if len(jsonData.Routes) > 0 {
meta.Routes = make([]pluginsv0alpha1.PluginMetaRoute, 0, len(jsonData.Routes))
for _, route := range jsonData.Routes {
v0Route := pluginsv0alpha1.PluginMetaRoute{}
if route.Path != "" {
v0Route.Path = &route.Path
}
if route.Method != "" {
v0Route.Method = &route.Method
}
if route.URL != "" {
v0Route.Url = &route.URL
}
if route.ReqRole != "" {
reqRole := string(route.ReqRole)
v0Route.ReqRole = &reqRole
}
if route.ReqAction != "" {
v0Route.ReqAction = &route.ReqAction
}
if len(route.Headers) > 0 {
headers := make([]string, 0, len(route.Headers))
for _, header := range route.Headers {
headers = append(headers, header.Name+": "+header.Content)
}
v0Route.Headers = headers
}
if len(route.URLParams) > 0 {
v0Route.UrlParams = make([]pluginsv0alpha1.PluginMetaV0alpha1RouteUrlParams, 0, len(route.URLParams))
for _, param := range route.URLParams {
v0Param := pluginsv0alpha1.PluginMetaV0alpha1RouteUrlParams{}
if param.Name != "" {
v0Param.Name = &param.Name
}
if param.Content != "" {
v0Param.Content = &param.Content
}
v0Route.UrlParams = append(v0Route.UrlParams, v0Param)
}
}
if route.TokenAuth != nil {
v0Route.TokenAuth = &pluginsv0alpha1.PluginMetaV0alpha1RouteTokenAuth{}
if route.TokenAuth.Url != "" {
v0Route.TokenAuth.Url = &route.TokenAuth.Url
}
if len(route.TokenAuth.Scopes) > 0 {
v0Route.TokenAuth.Scopes = route.TokenAuth.Scopes
}
if len(route.TokenAuth.Params) > 0 {
v0Route.TokenAuth.Params = make(map[string]interface{})
for k, v := range route.TokenAuth.Params {
v0Route.TokenAuth.Params[k] = v
}
}
}
if route.JwtTokenAuth != nil {
v0Route.JwtTokenAuth = &pluginsv0alpha1.PluginMetaV0alpha1RouteJwtTokenAuth{}
if route.JwtTokenAuth.Url != "" {
v0Route.JwtTokenAuth.Url = &route.JwtTokenAuth.Url
}
if len(route.JwtTokenAuth.Scopes) > 0 {
v0Route.JwtTokenAuth.Scopes = route.JwtTokenAuth.Scopes
}
if len(route.JwtTokenAuth.Params) > 0 {
v0Route.JwtTokenAuth.Params = make(map[string]interface{})
for k, v := range route.JwtTokenAuth.Params {
v0Route.JwtTokenAuth.Params[k] = v
}
}
}
if len(route.Body) > 0 {
var bodyMap map[string]interface{}
if err := json.Unmarshal(route.Body, &bodyMap); err == nil {
v0Route.Body = bodyMap
}
}
meta.Routes = append(meta.Routes, v0Route)
}
}
// Map Extensions
if len(jsonData.Extensions.AddedLinks) > 0 || len(jsonData.Extensions.AddedComponents) > 0 ||
len(jsonData.Extensions.ExposedComponents) > 0 || len(jsonData.Extensions.ExtensionPoints) > 0 {
extensions := &pluginsv0alpha1.PluginMetaExtensions{}
if len(jsonData.Extensions.AddedLinks) > 0 {
extensions.AddedLinks = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedLinks, 0, len(jsonData.Extensions.AddedLinks))
for _, link := range jsonData.Extensions.AddedLinks {
v0Link := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedLinks{
Targets: link.Targets,
Title: link.Title,
}
if link.Description != "" {
v0Link.Description = &link.Description
}
extensions.AddedLinks = append(extensions.AddedLinks, v0Link)
}
}
if len(jsonData.Extensions.AddedComponents) > 0 {
extensions.AddedComponents = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedComponents, 0, len(jsonData.Extensions.AddedComponents))
for _, comp := range jsonData.Extensions.AddedComponents {
v0Comp := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsAddedComponents{
Targets: comp.Targets,
Title: comp.Title,
}
if comp.Description != "" {
v0Comp.Description = &comp.Description
}
extensions.AddedComponents = append(extensions.AddedComponents, v0Comp)
}
}
if len(jsonData.Extensions.ExposedComponents) > 0 {
extensions.ExposedComponents = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExposedComponents, 0, len(jsonData.Extensions.ExposedComponents))
for _, comp := range jsonData.Extensions.ExposedComponents {
v0Comp := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExposedComponents{
Id: comp.Id,
}
if comp.Title != "" {
v0Comp.Title = &comp.Title
}
if comp.Description != "" {
v0Comp.Description = &comp.Description
}
extensions.ExposedComponents = append(extensions.ExposedComponents, v0Comp)
}
}
if len(jsonData.Extensions.ExtensionPoints) > 0 {
extensions.ExtensionPoints = make([]pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExtensionPoints, 0, len(jsonData.Extensions.ExtensionPoints))
for _, point := range jsonData.Extensions.ExtensionPoints {
v0Point := pluginsv0alpha1.PluginMetaV0alpha1ExtensionsExtensionPoints{
Id: point.Id,
}
if point.Title != "" {
v0Point.Title = &point.Title
}
if point.Description != "" {
v0Point.Description = &point.Description
}
extensions.ExtensionPoints = append(extensions.ExtensionPoints, v0Point)
}
}
meta.Extensions = extensions
}
// Map Roles
if len(jsonData.Roles) > 0 {
meta.Roles = make([]pluginsv0alpha1.PluginMetaRole, 0, len(jsonData.Roles))
for _, role := range jsonData.Roles {
v0Role := pluginsv0alpha1.PluginMetaRole{
Grants: role.Grants,
}
if role.Role.Name != "" || role.Role.Description != "" || len(role.Role.Permissions) > 0 {
v0RoleRole := &pluginsv0alpha1.PluginMetaV0alpha1RoleRole{}
if role.Role.Name != "" {
v0RoleRole.Name = &role.Role.Name
}
if role.Role.Description != "" {
v0RoleRole.Description = &role.Role.Description
}
if len(role.Role.Permissions) > 0 {
v0RoleRole.Permissions = make([]pluginsv0alpha1.PluginMetaV0alpha1RoleRolePermissions, 0, len(role.Role.Permissions))
for _, perm := range role.Role.Permissions {
v0Perm := pluginsv0alpha1.PluginMetaV0alpha1RoleRolePermissions{}
if perm.Action != "" {
v0Perm.Action = &perm.Action
}
if perm.Scope != "" {
v0Perm.Scope = &perm.Scope
}
v0RoleRole.Permissions = append(v0RoleRole.Permissions, v0Perm)
}
}
v0Role.Role = v0RoleRole
}
meta.Roles = append(meta.Roles, v0Role)
}
}
// Map IAM
if jsonData.IAM != nil && len(jsonData.IAM.Permissions) > 0 {
iam := &pluginsv0alpha1.PluginMetaIAM{
Permissions: make([]pluginsv0alpha1.PluginMetaV0alpha1IAMPermissions, 0, len(jsonData.IAM.Permissions)),
}
for _, perm := range jsonData.IAM.Permissions {
v0Perm := pluginsv0alpha1.PluginMetaV0alpha1IAMPermissions{}
if perm.Action != "" {
v0Perm.Action = &perm.Action
}
if perm.Scope != "" {
v0Perm.Scope = &perm.Scope
}
iam.Permissions = append(iam.Permissions, v0Perm)
}
meta.Iam = iam
}
return meta
}
+24 -18
View File
@@ -22,14 +22,16 @@ func TestCoreProvider_GetMeta(t *testing.T) {
t.Run("returns cached plugin when available", func(t *testing.T) {
provider := NewCoreProvider()
expectedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedSpec := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
provider.mu.Lock()
provider.loadedPlugins["test-plugin"] = expectedMeta
provider.loadedPlugins["test-plugin"] = expectedSpec
provider.initialized = true
provider.mu.Unlock()
@@ -37,7 +39,7 @@ func TestCoreProvider_GetMeta(t *testing.T) {
require.NoError(t, err)
require.NotNil(t, result)
assert.Equal(t, expectedMeta, result.Meta)
assert.Equal(t, expectedSpec, result.Meta)
assert.Equal(t, defaultCoreTTL, result.TTL)
})
@@ -58,14 +60,16 @@ func TestCoreProvider_GetMeta(t *testing.T) {
t.Run("ignores version parameter", func(t *testing.T) {
provider := NewCoreProvider()
expectedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedSpec := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
provider.mu.Lock()
provider.loadedPlugins["test-plugin"] = expectedMeta
provider.loadedPlugins["test-plugin"] = expectedSpec
provider.initialized = true
provider.mu.Unlock()
@@ -81,14 +85,16 @@ func TestCoreProvider_GetMeta(t *testing.T) {
customTTL := 2 * time.Hour
provider := NewCoreProviderWithTTL(customTTL)
expectedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedSpec := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
provider.mu.Lock()
provider.loadedPlugins["test-plugin"] = expectedMeta
provider.loadedPlugins["test-plugin"] = expectedSpec
provider.initialized = true
provider.mu.Unlock()
@@ -226,8 +232,8 @@ func TestCoreProvider_loadPlugins(t *testing.T) {
if loaded {
result, err := provider.GetMeta(ctx, "test-datasource", "1.0.0")
require.NoError(t, err)
assert.Equal(t, "test-datasource", result.Meta.Id)
assert.Equal(t, "Test Datasource", result.Meta.Name)
assert.Equal(t, "test-datasource", result.Meta.PluginJSON.Id)
assert.Equal(t, "Test Datasource", result.Meta.PluginJSON.Name)
}
})
}
+53
View File
@@ -0,0 +1,53 @@
package meta
import (
"context"
"time"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
)
const (
defaultLocalTTL = 1 * time.Hour
)
// PluginAssetsCalculator is an interface for calculating plugin asset information.
// LocalProvider requires this to calculate loading strategy and module hash.
type PluginAssetsCalculator interface {
LoadingStrategy(ctx context.Context, p pluginstore.Plugin) plugins.LoadingStrategy
ModuleHash(ctx context.Context, p pluginstore.Plugin) string
}
// LocalProvider retrieves plugin metadata for locally installed plugins.
// It uses the plugin store to access plugins that have already been loaded.
type LocalProvider struct {
store pluginstore.Store
pluginAssets PluginAssetsCalculator
}
// NewLocalProvider creates a new LocalProvider for locally installed plugins.
// pluginAssets is required for calculating loading strategy and module hash.
func NewLocalProvider(pluginStore pluginstore.Store, pluginAssets PluginAssetsCalculator) *LocalProvider {
return &LocalProvider{
store: pluginStore,
pluginAssets: pluginAssets,
}
}
// GetMeta retrieves plugin metadata for locally installed plugins.
func (p *LocalProvider) GetMeta(ctx context.Context, pluginID, version string) (*Result, error) {
plugin, exists := p.store.Plugin(ctx, pluginID)
if !exists {
return nil, ErrMetaNotFound
}
loadingStrategy := p.pluginAssets.LoadingStrategy(ctx, plugin)
moduleHash := p.pluginAssets.ModuleHash(ctx, plugin)
spec := pluginStorePluginToPluginMetaSpec(plugin, loadingStrategy, moduleHash)
return &Result{
Meta: spec,
TTL: defaultLocalTTL,
}, nil
}
+2 -2
View File
@@ -16,7 +16,7 @@ const (
// cachedMeta represents a cached metadata entry with expiration time
type cachedMeta struct {
meta pluginsv0alpha1.PluginMetaJSONData
meta pluginsv0alpha1.PluginMetaSpec
ttl time.Duration
expiresAt time.Time
}
@@ -84,7 +84,7 @@ func (pm *ProviderManager) GetMeta(ctx context.Context, pluginID, version string
if err == nil {
// Don't cache results with a zero TTL
if result.TTL == 0 {
continue
return result, nil
}
pm.cacheMu.Lock()
+61 -41
View File
@@ -35,10 +35,12 @@ func TestProviderManager_GetMeta(t *testing.T) {
ctx := context.Background()
t.Run("returns cached result when available and not expired", func(t *testing.T) {
cachedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
cachedMeta := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
provider := &mockProvider{
@@ -60,8 +62,10 @@ func TestProviderManager_GetMeta(t *testing.T) {
provider.getMetaFunc = func(ctx context.Context, pluginID, version string) (*Result, error) {
return &Result{
Meta: pluginsv0alpha1.PluginMetaJSONData{Id: "different"},
TTL: time.Hour,
Meta: pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{Id: "different"},
},
TTL: time.Hour,
}, nil
}
@@ -73,10 +77,12 @@ func TestProviderManager_GetMeta(t *testing.T) {
})
t.Run("fetches from provider when not cached", func(t *testing.T) {
expectedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedMeta := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
expectedTTL := 2 * time.Hour
@@ -108,15 +114,19 @@ func TestProviderManager_GetMeta(t *testing.T) {
})
t.Run("does not cache result with zero TTL and tries next provider", func(t *testing.T) {
zeroTTLMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Zero TTL Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
zeroTTLMeta := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Zero TTL Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
expectedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedMeta := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
provider1 := &mockProvider{
@@ -154,10 +164,12 @@ func TestProviderManager_GetMeta(t *testing.T) {
})
t.Run("tries next provider when first returns ErrMetaNotFound", func(t *testing.T) {
expectedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedMeta := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
provider1 := &mockProvider{
@@ -229,15 +241,19 @@ func TestProviderManager_GetMeta(t *testing.T) {
})
t.Run("skips expired cache entries", func(t *testing.T) {
expiredMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Expired Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expiredMeta := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Expired Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
expectedMeta := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedMeta := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Test Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
callCount := 0
@@ -272,15 +288,19 @@ func TestProviderManager_GetMeta(t *testing.T) {
})
t.Run("uses first successful provider", func(t *testing.T) {
expectedMeta1 := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Provider 1 Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedMeta1 := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Provider 1 Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
expectedMeta2 := pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Provider 2 Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
expectedMeta2 := pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginsv0alpha1.PluginMetaJSONData{
Id: "test-plugin",
Name: "Provider 2 Plugin",
Type: pluginsv0alpha1.PluginMetaJSONDataTypeDatasource,
},
}
provider1 := &mockProvider{
@@ -331,9 +351,9 @@ func TestProviderManager_Run(t *testing.T) {
func TestProviderManager_cleanupExpired(t *testing.T) {
t.Run("removes expired entries", func(t *testing.T) {
validMeta := pluginsv0alpha1.PluginMetaJSONData{Id: "valid"}
expiredMeta1 := pluginsv0alpha1.PluginMetaJSONData{Id: "expired1"}
expiredMeta2 := pluginsv0alpha1.PluginMetaJSONData{Id: "expired2"}
validMeta := pluginsv0alpha1.PluginMetaSpec{PluginJSON: pluginsv0alpha1.PluginMetaJSONData{Id: "valid"}}
expiredMeta1 := pluginsv0alpha1.PluginMetaSpec{PluginJSON: pluginsv0alpha1.PluginMetaJSONData{Id: "expired1"}}
expiredMeta2 := pluginsv0alpha1.PluginMetaSpec{PluginJSON: pluginsv0alpha1.PluginMetaJSONData{Id: "expired2"}}
provider := &mockProvider{
getMetaFunc: func(ctx context.Context, pluginID, version string) (*Result, error) {
+2 -2
View File
@@ -14,14 +14,14 @@ var (
// Result contains plugin metadata along with its recommended TTL.
type Result struct {
Meta pluginsv0alpha1.PluginMetaJSONData
Meta pluginsv0alpha1.PluginMetaSpec
TTL time.Duration
}
// Provider is used for retrieving plugin metadata.
type Provider interface {
// GetMeta retrieves plugin metadata for the given plugin ID and version.
// Returns the Result containing the PluginMetaJSONData and its recommended TTL.
// Returns the Result containing the PluginMetaSpec and its recommended TTL.
// If the plugin is not found, returns ErrMetaNotFound.
GetMeta(ctx context.Context, pluginID, version string) (*Result, error)
}
+5 -7
View File
@@ -126,7 +126,7 @@ func (s *PluginMetaStorage) List(ctx context.Context, options *internalversion.L
continue
}
pluginMeta := createPluginMetaFromPluginMetaJSONData(result.Meta, plugin.Name, plugin.Namespace)
pluginMeta := createPluginMetaFromSpec(result.Meta, plugin.Name, plugin.Namespace)
metaItems = append(metaItems, *pluginMeta)
}
@@ -174,19 +174,17 @@ func (s *PluginMetaStorage) Get(ctx context.Context, name string, options *metav
return nil, apierrors.NewInternalError(fmt.Errorf("failed to fetch plugin metadata: %w", err))
}
return createPluginMetaFromPluginMetaJSONData(result.Meta, name, ns.Value), nil
return createPluginMetaFromSpec(result.Meta, name, ns.Value), nil
}
// createPluginMetaFromPluginMetaJSONData creates a PluginMeta k8s object from PluginMetaJSONData and plugin metadata.
func createPluginMetaFromPluginMetaJSONData(pluginJSON pluginsv0alpha1.PluginMetaJSONData, name, namespace string) *pluginsv0alpha1.PluginMeta {
// createPluginMetaFromSpec creates a PluginMeta k8s object from PluginMetaSpec and plugin metadata.
func createPluginMetaFromSpec(spec pluginsv0alpha1.PluginMetaSpec, name, namespace string) *pluginsv0alpha1.PluginMeta {
pluginMeta := &pluginsv0alpha1.PluginMeta{
ObjectMeta: metav1.ObjectMeta{
Name: name,
Namespace: namespace,
},
Spec: pluginsv0alpha1.PluginMetaSpec{
PluginJSON: pluginJSON,
},
Spec: spec,
}
// Set the GroupVersionKind