Plugins: Add /meta and /metas APIs to plugins app (#113775)
* add /meta and /metas APIs * wrapped storage route * format file * fix switch statement lint issue * fix plugininstaller test --------- Co-authored-by: Todd Treece <todd.treece@grafana.com>
This commit is contained in:
@@ -0,0 +1,131 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pluginsv0alpha1 "github.com/grafana/grafana/apps/plugins/pkg/apis/plugins/v0alpha1"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultCleanupInterval = 10 * time.Minute
|
||||
)
|
||||
|
||||
// cachedMeta represents a cached metadata entry with expiration time
|
||||
type cachedMeta struct {
|
||||
meta pluginsv0alpha1.PluginMetaJSONData
|
||||
ttl time.Duration
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
// ProviderManager searches multiple providers for Plugin Meta in order until one succeeds, and caches
|
||||
// results with per-provider TTLs.
|
||||
// It implements app.Runnable to manage the cleanup goroutine lifecycle.
|
||||
type ProviderManager struct {
|
||||
providers []Provider
|
||||
cache map[string]*cachedMeta
|
||||
cacheMu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewProviderManager creates a new ProviderManager that chains the given providers
|
||||
// and caches results with per-provider TTLs.
|
||||
func NewProviderManager(providers ...Provider) *ProviderManager {
|
||||
if len(providers) == 0 {
|
||||
panic("ProviderManager requires at least one provider")
|
||||
}
|
||||
|
||||
return &ProviderManager{
|
||||
providers: providers,
|
||||
cache: make(map[string]*cachedMeta),
|
||||
}
|
||||
}
|
||||
|
||||
// Run implements app.Runnable. It runs the cleanup loop until the context is cancelled.
|
||||
// This method blocks until the context is cancelled (when the app shuts down).
|
||||
func (pm *ProviderManager) Run(ctx context.Context) error {
|
||||
ticker := time.NewTicker(defaultCleanupInterval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil
|
||||
case <-ticker.C:
|
||||
pm.cleanupExpired()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetMeta tries each provider in order until one succeeds, using cache when available.
|
||||
// Returns ErrMetaNotFound only if all providers return ErrMetaNotFound.
|
||||
// Otherwise, returns the last non-ErrMetaNotFound error if all providers fail.
|
||||
func (pm *ProviderManager) GetMeta(ctx context.Context, pluginID, version string) (*Result, error) {
|
||||
cacheKey := pm.cacheKey(pluginID, version)
|
||||
|
||||
// Check cache first
|
||||
pm.cacheMu.RLock()
|
||||
cached, exists := pm.cache[cacheKey]
|
||||
pm.cacheMu.RUnlock()
|
||||
|
||||
if exists && time.Now().Before(cached.expiresAt) {
|
||||
return &Result{
|
||||
Meta: cached.meta,
|
||||
TTL: cached.ttl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Try each provider in order until one succeeds
|
||||
var lastErr error
|
||||
for _, provider := range pm.providers {
|
||||
result, err := provider.GetMeta(ctx, pluginID, version)
|
||||
if err == nil {
|
||||
// Don't cache results with a zero TTL
|
||||
if result.TTL == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
pm.cacheMu.Lock()
|
||||
pm.cache[cacheKey] = &cachedMeta{
|
||||
meta: result.Meta,
|
||||
ttl: result.TTL,
|
||||
expiresAt: time.Now().Add(result.TTL),
|
||||
}
|
||||
pm.cacheMu.Unlock()
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// If not found, try next provider
|
||||
if errors.Is(err, ErrMetaNotFound) {
|
||||
continue
|
||||
}
|
||||
|
||||
lastErr = err
|
||||
}
|
||||
|
||||
if lastErr != nil {
|
||||
return nil, fmt.Errorf("failed to fetch plugin metadata from any provider: %w", lastErr)
|
||||
}
|
||||
|
||||
return nil, ErrMetaNotFound
|
||||
}
|
||||
|
||||
// cleanupExpired removes expired entries from the cache.
|
||||
func (pm *ProviderManager) cleanupExpired() {
|
||||
now := time.Now()
|
||||
|
||||
pm.cacheMu.Lock()
|
||||
defer pm.cacheMu.Unlock()
|
||||
for key, entry := range pm.cache {
|
||||
if now.After(entry.expiresAt) {
|
||||
delete(pm.cache, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (pm *ProviderManager) cacheKey(pluginID, version string) string {
|
||||
return fmt.Sprintf("%s:%s", pluginID, version)
|
||||
}
|
||||
Reference in New Issue
Block a user