mirror of
https://github.com/grafana/grafana.git
synced 2025-02-10 23:55:47 -06:00
* WIP: Plugins tracing * Trace ID middleware * Add prometheus metrics and tracing to plugins updater * Add TODOs * Add instrumented http client * Add tracing to grafana update checker * Goimports * Moved plugins tracing to middleware * goimports, fix tests * Removed X-Trace-Id header * Fix comment in NewTracingHeaderMiddleware * Add metrics to instrumented http client * Add instrumented http client options * Removed unused function * Switch to contextual logger * Refactoring, fix tests * Moved InstrumentedHTTPClient and PrometheusMetrics to their own package * Tracing middleware: handle errors * Report span status codes when recording errors * Add tests for tracing middleware * Moved fakeSpan and fakeTracer to pkg/infra/tracing * Add TestHTTPClientTracing * Lint * Changes after PR review * Tests: Made "ended" in FakeSpan private, allow calling End only once * Testing: panic in FakeSpan if span already ended * Refactoring: Simplify Grafana updater checks * Refactoring: Simplify plugins updater error checks and logs * Fix wrong call to checkForUpdates -> instrumentedCheckForUpdates * Tests: Fix wrong call to checkForUpdates -> instrumentedCheckForUpdates * Log update checks duration, use Info log level for check succeeded logs * Add plugin context span attributes in tracing_middleware * Refactor prometheus metrics as httpclient middleware * Fix call to ProvidePluginsService in plugins_test.go * Propagate context to update checker outgoing http requests * Plugin client tracing middleware: Removed operation name in status * Fix tests * Goimports tracing_middleware.go * Goimports * Fix imports * Changed span name to plugins client middleware * Add span name assertion in TestTracingMiddleware * Removed Prometheus metrics middleware from grafana and plugins updatechecker * Add span attributes for ds name, type, uid, panel and dashboard ids * Fix http header reading in tracing middlewares * Use contexthandler.FromContext, add X-Query-Group-Id * Add test for RunStream * Fix imports * Changes from PR review * TestTracingMiddleware: Changed assert to require for didPanic assertion * Lint * Fix imports
153 lines
3.8 KiB
Go
153 lines
3.8 KiB
Go
package updatechecker
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
|
"github.com/hashicorp/go-version"
|
|
"go.opentelemetry.io/otel/codes"
|
|
|
|
"github.com/grafana/grafana/pkg/infra/httpclient/httpclientprovider"
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
)
|
|
|
|
const grafanaLatestJSONURL = "https://raw.githubusercontent.com/grafana/grafana/main/latest.json"
|
|
|
|
type GrafanaService struct {
|
|
hasUpdate bool
|
|
latestVersion string
|
|
|
|
enabled bool
|
|
grafanaVersion string
|
|
httpClient httpClient
|
|
mutex sync.RWMutex
|
|
log log.Logger
|
|
tracer tracing.Tracer
|
|
}
|
|
|
|
func ProvideGrafanaService(cfg *setting.Cfg, tracer tracing.Tracer) (*GrafanaService, error) {
|
|
logger := log.New("grafana.update.checker")
|
|
cl, err := httpclient.New(httpclient.Options{
|
|
Middlewares: []httpclient.Middleware{
|
|
httpclientprovider.TracingMiddleware(logger, tracer),
|
|
},
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &GrafanaService{
|
|
enabled: cfg.CheckForGrafanaUpdates,
|
|
grafanaVersion: cfg.BuildVersion,
|
|
httpClient: cl,
|
|
log: logger,
|
|
tracer: tracer,
|
|
}, nil
|
|
}
|
|
|
|
func (s *GrafanaService) IsDisabled() bool {
|
|
return !s.enabled
|
|
}
|
|
|
|
func (s *GrafanaService) Run(ctx context.Context) error {
|
|
s.instrumentedCheckForUpdates(ctx)
|
|
|
|
ticker := time.NewTicker(time.Minute * 10)
|
|
run := true
|
|
|
|
for run {
|
|
select {
|
|
case <-ticker.C:
|
|
s.instrumentedCheckForUpdates(ctx)
|
|
case <-ctx.Done():
|
|
run = false
|
|
}
|
|
}
|
|
|
|
return ctx.Err()
|
|
}
|
|
|
|
func (s *GrafanaService) instrumentedCheckForUpdates(ctx context.Context) {
|
|
start := time.Now()
|
|
ctx, span := s.tracer.Start(ctx, "updatechecker.GrafanaService.checkForUpdates")
|
|
defer span.End()
|
|
ctxLogger := s.log.FromContext(ctx)
|
|
if err := s.checkForUpdates(ctx); err != nil {
|
|
span.SetStatus(codes.Error, fmt.Sprintf("update check failed: %s", err))
|
|
span.RecordError(err)
|
|
ctxLogger.Error("Update check failed", "error", err, "duration", time.Since(start))
|
|
return
|
|
}
|
|
ctxLogger.Info("Update check succeeded", "duration", time.Since(start))
|
|
}
|
|
|
|
func (s *GrafanaService) checkForUpdates(ctx context.Context) error {
|
|
ctxLogger := s.log.FromContext(ctx)
|
|
ctxLogger.Debug("Checking for updates")
|
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, grafanaLatestJSONURL, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := s.httpClient.Do(req)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get latest.json repo from github.com: %w", err)
|
|
}
|
|
defer func() {
|
|
if err := resp.Body.Close(); err != nil {
|
|
ctxLogger.Warn("Failed to close response body", "err", err)
|
|
}
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return fmt.Errorf("update check failed, reading response from github.com: %w", err)
|
|
}
|
|
|
|
type latestJSON struct {
|
|
Stable string `json:"stable"`
|
|
Testing string `json:"testing"`
|
|
}
|
|
var latest latestJSON
|
|
err = json.Unmarshal(body, &latest)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to unmarshal latest.json: %w", err)
|
|
}
|
|
|
|
s.mutex.Lock()
|
|
defer s.mutex.Unlock()
|
|
if strings.Contains(s.grafanaVersion, "-") {
|
|
s.latestVersion = latest.Testing
|
|
s.hasUpdate = !strings.HasPrefix(s.grafanaVersion, latest.Testing)
|
|
} else {
|
|
s.latestVersion = latest.Stable
|
|
s.hasUpdate = latest.Stable != s.grafanaVersion
|
|
}
|
|
|
|
currVersion, err1 := version.NewVersion(s.grafanaVersion)
|
|
latestVersion, err2 := version.NewVersion(s.latestVersion)
|
|
if err1 == nil && err2 == nil {
|
|
s.hasUpdate = currVersion.LessThan(latestVersion)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (s *GrafanaService) UpdateAvailable() bool {
|
|
s.mutex.RLock()
|
|
defer s.mutex.RUnlock()
|
|
return s.hasUpdate
|
|
}
|
|
|
|
func (s *GrafanaService) LatestVersion() string {
|
|
s.mutex.RLock()
|
|
defer s.mutex.RUnlock()
|
|
return s.latestVersion
|
|
}
|