Files
grafana/apps/plugins/pkg/app/meta/catalog.go
T
2025-12-17 16:25:54 +01:00

94 lines
2.5 KiB
Go

package meta
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"path"
"time"
"github.com/grafana/grafana-app-sdk/logging"
)
const (
defaultCatalogTTL = 1 * time.Hour
)
// CatalogProvider retrieves plugin metadata from the grafana.com API.
type CatalogProvider struct {
httpClient *http.Client
grafanaComAPIURL string
log logging.Logger
ttl time.Duration
}
// NewCatalogProvider creates a new CatalogProvider that fetches metadata from grafana.com.
func NewCatalogProvider(grafanaComAPIURL string) *CatalogProvider {
return NewCatalogProviderWithTTL(grafanaComAPIURL, defaultCatalogTTL)
}
// NewCatalogProviderWithTTL creates a new CatalogProvider with a custom TTL.
func NewCatalogProviderWithTTL(grafanaComAPIURL string, ttl time.Duration) *CatalogProvider {
if grafanaComAPIURL == "" {
grafanaComAPIURL = "https://grafana.com/api/plugins"
}
return &CatalogProvider{
httpClient: &http.Client{
Timeout: 10 * time.Second,
},
grafanaComAPIURL: grafanaComAPIURL,
log: logging.DefaultLogger,
ttl: ttl,
}
}
// GetMeta fetches plugin metadata from grafana.com API endpoint:
// GET /api/plugins/{pluginId}/versions/{version}
func (p *CatalogProvider) GetMeta(ctx context.Context, pluginID, version string) (*Result, error) {
u, err := url.Parse(p.grafanaComAPIURL)
if err != nil {
return nil, fmt.Errorf("invalid grafana.com API URL: %w", err)
}
u.Path = path.Join(u.Path, pluginID, "versions", version)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return nil, fmt.Errorf("failed to create request: %w", err)
}
req.Header.Set("Accept", "application/json")
req.Header.Set("User-Agent", "grafana-plugins-app")
resp, err := p.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to fetch plugin metadata: %w", err)
}
defer func() {
if err = resp.Body.Close(); err != nil {
p.log.Warn("Failed to close response body", "error", err)
}
}()
if resp.StatusCode == http.StatusNotFound {
return nil, ErrMetaNotFound
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected status code %d from grafana.com API", resp.StatusCode)
}
var gcomMeta grafanaComPluginVersionMeta
if err = json.NewDecoder(resp.Body).Decode(&gcomMeta); err != nil {
return nil, fmt.Errorf("failed to decode response: %w", err)
}
metaSpec := grafanaComPluginVersionMetaToMetaSpec(gcomMeta)
return &Result{
Meta: metaSpec,
TTL: p.ttl,
}, nil
}