diff --git a/devenv/frontend-service/Tiltfile b/devenv/frontend-service/Tiltfile index 9c443cf2ece..9ed8fa2e673 100644 --- a/devenv/frontend-service/Tiltfile +++ b/devenv/frontend-service/Tiltfile @@ -71,6 +71,7 @@ dc_resource("tempo", labels=["observability"]) dc_resource("postgres", labels=["misc"]) dc_resource("tempo-init", labels=["misc"]) +dc_resource("goff", labels=["misc"]) # paths in tilt files are confusing.... # - if tilt is dealing with the path, it is relative to the Tiltfile diff --git a/devenv/frontend-service/configs/nginx.conf b/devenv/frontend-service/configs/nginx.conf index aa0c2499656..301bfddda1d 100644 --- a/devenv/frontend-service/configs/nginx.conf +++ b/devenv/frontend-service/configs/nginx.conf @@ -105,6 +105,7 @@ server { proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header baggage "namespace=stacks-11"; } } diff --git a/devenv/frontend-service/docker-compose.yaml b/devenv/frontend-service/docker-compose.yaml index a7acd3359cd..ee6d9b34e36 100644 --- a/devenv/frontend-service/docker-compose.yaml +++ b/devenv/frontend-service/docker-compose.yaml @@ -78,10 +78,23 @@ services: OTEL_SERVICE_NAME: frontend-service GF_TRACING_OPENTELEMETRY_OTLP_ADDRESS: 'alloy:4317' GF_TRACING_OPENTELEMETRY_OTLP_PROPAGATION: jaeger,w3c + GF_FEATURE_TOGGLES_OPENFEATURE_PROVIDER: ofrep + GF_FEATURE_TOGGLES_OPENFEATURE_URL: http://goff:1031 # Faro (Grafana JavaScript Agent) configuration - sends frontend observability data to Loki via Alloy GF_LOG_FRONTEND_ENABLED: true GF_LOG_FRONTEND_CUSTOM_ENDPOINT: http://localhost:12347/collect + goff: + image: gofeatureflag/go-feature-flag:latest + command: /go-feature-flag --config /config/config.yaml + ports: + - 1031:1031/tcp + configs: + - source: goff_config + target: /config/config.yaml + - source: goff_flags + target: /flags/flags.yaml + postgres: image: postgres:16.1-alpine3.19@sha256:17eb369d9330fe7fbdb2f705418c18823d66322584c77c2b43cc0e1851d01de7 environment: @@ -155,6 +168,20 @@ services: tempo-init: condition: service_completed_successfully +configs: + goff_config: + content: | + logLevel: debug + logFormat: logfmt + listen: 1031 + evaluationContextEnrichment: + cluster: localdev + retrievers: + - kind: "file" + path: "/flags/flags.yaml" + goff_flags: + file: ./goff-flags.yaml + volumes: backend-data: postgres-data: diff --git a/devenv/frontend-service/goff-flags.yaml b/devenv/frontend-service/goff-flags.yaml new file mode 100644 index 00000000000..3e2a5d4a567 --- /dev/null +++ b/devenv/frontend-service/goff-flags.yaml @@ -0,0 +1,6 @@ +flagName: + variations: + enabled: true + disabled: false + defaultRule: + variation: disabled diff --git a/pkg/services/frontend/context_middleware.go b/pkg/services/frontend/context_middleware.go index 31f61c8a394..149b4360e84 100644 --- a/pkg/services/frontend/context_middleware.go +++ b/pkg/services/frontend/context_middleware.go @@ -6,7 +6,10 @@ import ( "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" + "github.com/open-feature/go-sdk/openfeature" + "k8s.io/apiserver/pkg/endpoints/request" + "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/trace" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" @@ -37,8 +40,9 @@ func setRequestContext(ctx context.Context, w http.ResponseWriter, r *http.Reque ctx, span := tracing.Start(ctx, "setRequestContext") defer span.End() + webCtx := web.FromContext(ctx) reqContext := &contextmodel.ReqContext{ - Context: web.FromContext(ctx), + Context: webCtx, Logger: log.New("context"), SignedInUser: &user.SignedInUser{}, } @@ -48,7 +52,9 @@ func setRequestContext(ctx context.Context, w http.ResponseWriter, r *http.Reque // Set the context for the http.Request.Context // This modifies both r and reqContext.Req since they point to the same value - *reqContext.Req = *reqContext.Req.WithContext(ctx) + if webCtx != nil { + *reqContext.Req = *reqContext.Req.WithContext(ctx) + } // add traceID to logger context traceID := tracing.TraceIDFromContext(ctx, false) @@ -64,5 +70,27 @@ func setRequestContext(ctx context.Context, w http.ResponseWriter, r *http.Reque reqContext.Logger = reqContext.Logger.New("hostname", hostname) } + // Parse namespace from W3C baggage header + var namespace string + if baggageHeader := r.Header.Get("baggage"); baggageHeader != "" { + if bag, err := baggage.Parse(baggageHeader); err == nil { + if member := bag.Member("namespace"); member.Value() != "" { + namespace = member.Value() + } + } + } + ctx = request.WithNamespace(ctx, namespace) + + // Note: OpenFeature is already initialized by target.go before this service starts. + // The frontend service only needs to set evaluation context per request + openFeatureNamespace := "default" + if namespace != "" { + openFeatureNamespace = namespace + } + evalCtx := openfeature.NewEvaluationContext(openFeatureNamespace, map[string]any{ + "namespace": openFeatureNamespace, + }) + ctx = openfeature.MergeTransactionContext(ctx, evalCtx) + return ctx } diff --git a/pkg/services/frontend/context_middleware_test.go b/pkg/services/frontend/context_middleware_test.go new file mode 100644 index 00000000000..7d44d8e82f6 --- /dev/null +++ b/pkg/services/frontend/context_middleware_test.go @@ -0,0 +1,96 @@ +package frontend + +import ( + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "k8s.io/apiserver/pkg/endpoints/request" +) + +func TestContextMiddleware(t *testing.T) { + t.Run("calls next handler with context", func(t *testing.T) { + service := &frontendService{} + nextCalled := false + var capturedRequest *http.Request + + nextHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + nextCalled = true + capturedRequest = r + }) + + middleware := service.contextMiddleware() + handler := middleware(nextHandler) + + req := httptest.NewRequest(http.MethodGet, "/", nil) + recorder := httptest.NewRecorder() + + handler.ServeHTTP(recorder, req) + + assert.True(t, nextCalled, "next handler should be called") + assert.NotNil(t, capturedRequest, "request should be passed to next handler") + assert.NotNil(t, capturedRequest.Context(), "request context should be set") + }) +} + +func TestSetRequestContext(t *testing.T) { + t.Run("parses namespace from baggage header", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("baggage", "namespace=stacks-123") + rec := httptest.NewRecorder() + + ctx := setRequestContext(req.Context(), rec, req) + + namespace, ok := request.NamespaceFrom(ctx) + assert.True(t, ok, "Namespace should be present in context") + assert.Equal(t, "stacks-123", namespace, "Namespace should match baggage value") + }) + + t.Run("handles baggage header with multiple members", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("baggage", "trace-id=abc123,namespace=tenant-456,user-id=xyz") + rec := httptest.NewRecorder() + + ctx := setRequestContext(req.Context(), rec, req) + + namespace, ok := request.NamespaceFrom(ctx) + assert.True(t, ok, "Namespace should be present in context") + assert.Equal(t, "tenant-456", namespace, "Namespace should match baggage value") + }) + + t.Run("defaults to empty namespace when baggage header is missing", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + rec := httptest.NewRecorder() + + ctx := setRequestContext(req.Context(), rec, req) + + namespace, ok := request.NamespaceFrom(ctx) + assert.True(t, ok, "Namespace should be present in context") + assert.Empty(t, namespace, "Namespace should be empty when not provided") + }) + + t.Run("handles invalid baggage header gracefully", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("baggage", "invalid-baggage-format;;;") + rec := httptest.NewRecorder() + + ctx := setRequestContext(req.Context(), rec, req) + + namespace, ok := request.NamespaceFrom(ctx) + assert.True(t, ok, "Namespace should be present in context") + assert.Empty(t, namespace, "Namespace should be empty when baggage is invalid") + }) + + t.Run("handles baggage header without namespace member", func(t *testing.T) { + req := httptest.NewRequest(http.MethodGet, "/", nil) + req.Header.Set("baggage", "other-key=other-value") + rec := httptest.NewRecorder() + + ctx := setRequestContext(req.Context(), rec, req) + + namespace, ok := request.NamespaceFrom(ctx) + assert.True(t, ok, "Namespace should be present in context") + assert.Empty(t, namespace, "Namespace should be empty when namespace member not in baggage") + }) +} diff --git a/pkg/services/frontend/frontend_service.go b/pkg/services/frontend/frontend_service.go index e8951d86a1e..9a886862bf5 100644 --- a/pkg/services/frontend/frontend_service.go +++ b/pkg/services/frontend/frontend_service.go @@ -19,7 +19,6 @@ import ( "github.com/grafana/grafana/pkg/middleware/loggermw" "github.com/grafana/grafana/pkg/middleware/requestmeta" "github.com/grafana/grafana/pkg/services/featuremgmt" - fswebassets "github.com/grafana/grafana/pkg/services/frontend/webassets" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/licensing" publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api" @@ -56,12 +55,8 @@ type frontendService struct { func ProvideFrontendService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, promGatherer prometheus.Gatherer, promRegister prometheus.Registerer, license licensing.Licensing, hooksService *hooks.HooksService) (*frontendService, error) { logger := log.New("frontend-service") - assetsManifest, err := fswebassets.GetWebAssets(cfg, license) - if err != nil { - return nil, err - } - index, err := NewIndexProvider(cfg, assetsManifest, license, hooksService) + index, err := NewIndexProvider(cfg, license, hooksService) if err != nil { return nil, err } diff --git a/pkg/services/frontend/index.go b/pkg/services/frontend/index.go index b64b7be276c..d93568abf78 100644 --- a/pkg/services/frontend/index.go +++ b/pkg/services/frontend/index.go @@ -12,6 +12,7 @@ import ( "github.com/grafana/grafana/pkg/api/dtos" "github.com/grafana/grafana/pkg/services/contexthandler" contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model" + fswebassets "github.com/grafana/grafana/pkg/services/frontend/webassets" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/licensing" "github.com/grafana/grafana/pkg/setting" @@ -22,7 +23,7 @@ type IndexProvider struct { index *template.Template hooksService *hooks.HooksService config *setting.Cfg - assets dtos.EntryPointAssets // Includes CDN info + license licensing.Licensing } type IndexViewData struct { @@ -53,7 +54,7 @@ var ( htmlTemplates = template.Must(template.New("html").Delims("[[", "]]").ParseFS(templatesFS, `*.html`)) ) -func NewIndexProvider(cfg *setting.Cfg, assetsManifest dtos.EntryPointAssets, license licensing.Licensing, hooksService *hooks.HooksService) (*IndexProvider, error) { +func NewIndexProvider(cfg *setting.Cfg, license licensing.Licensing, hooksService *hooks.HooksService) (*IndexProvider, error) { t := htmlTemplates.Lookup("index.html") if t == nil { return nil, fmt.Errorf("missing index template") @@ -69,7 +70,7 @@ func NewIndexProvider(cfg *setting.Cfg, assetsManifest dtos.EntryPointAssets, li index: t, hooksService: hooksService, config: cfg, - assets: assetsManifest, + license: license, }, nil } @@ -89,6 +90,13 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http. return } + assetsManifest, err := fswebassets.GetWebAssets(ctx, p.config, p.license) + if err != nil { + p.log.Error("unable to get web assets", "err", err) + http.Error(writer, "Internal Server Error", http.StatusInternalServerError) + return + } + reqCtx := contexthandler.FromContext(ctx) // make a copy of the settings @@ -98,7 +106,7 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http. AppTitle: "Grafana", AppSubUrl: p.config.AppSubURL, IsDevelopmentEnv: p.config.Env == setting.Dev, - Assets: p.assets, + Assets: assetsManifest, DefaultUser: dtos.CurrentUser{}, Nonce: reqCtx.RequestNonce, PublicDashboardAccessToken: reqCtx.PublicDashboardAccessToken, diff --git a/pkg/services/frontend/request_config_middleware.go b/pkg/services/frontend/request_config_middleware.go index b29ce171646..84c175bb3ba 100644 --- a/pkg/services/frontend/request_config_middleware.go +++ b/pkg/services/frontend/request_config_middleware.go @@ -3,7 +3,6 @@ package frontend import ( "net/http" - "go.opentelemetry.io/otel/baggage" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apiserver/pkg/endpoints/request" @@ -29,17 +28,6 @@ func RequestConfigMiddleware(cfg *setting.Cfg, license licensing.Licensing, sett ctx, span := tracing.Start(r.Context(), "frontend.RequestConfigMiddleware") defer span.End() - // Parse namespace from W3C baggage header - var namespace string - if baggageHeader := r.Header.Get("baggage"); baggageHeader != "" { - if bag, err := baggage.Parse(baggageHeader); err == nil { - if member := bag.Member("namespace"); member.Value() != "" { - namespace = member.Value() - } - } - } - ctx = request.WithNamespace(ctx, namespace) - reqCtx := contexthandler.FromContext(ctx) logger := reqCtx.Logger @@ -48,28 +36,30 @@ func RequestConfigMiddleware(cfg *setting.Cfg, license licensing.Licensing, sett requestConfig := NewFSRequestConfig(cfg, license) // Fetch tenant-specific configuration if namespace is present - if namespace != "" && settingsService != nil { - // Fetch tenant overrides for relevant sections only - selector := metav1.LabelSelector{ - MatchExpressions: []metav1.LabelSelectorRequirement{{ - Key: "section", - Operator: metav1.LabelSelectorOpIn, - Values: []string{"security"}, // TODO: get correct list - }, { - // don't return values from defaults.ini as they conflict with the services's own defaults - Key: "source", - Operator: metav1.LabelSelectorOpNotIn, - Values: []string{"defaults"}, - }}, - } + if namespace, ok := request.NamespaceFrom(ctx); ok { + if settingsService != nil { + // Fetch tenant overrides for relevant sections only + selector := metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{{ + Key: "section", + Operator: metav1.LabelSelectorOpIn, + Values: []string{"security"}, // TODO: get correct list + }, { + // don't return values from defaults.ini as they conflict with the services's own defaults + Key: "source", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"defaults"}, + }}, + } - settings, err := settingsService.ListAsIni(ctx, selector) - if err != nil { - logger.Error("failed to fetch tenant settings", "namespace", namespace, "err", err) - // Fall back to base config - } else { - // Merge tenant overrides with base config - requestConfig.ApplyOverrides(settings, logger) + settings, err := settingsService.ListAsIni(ctx, selector) + if err != nil { + logger.Error("failed to fetch tenant settings", "namespace", namespace, "err", err) + // Fall back to base config + } else { + // Merge tenant overrides with base config + requestConfig.ApplyOverrides(settings, logger) + } } } diff --git a/pkg/services/frontend/request_config_middleware_test.go b/pkg/services/frontend/request_config_middleware_test.go index 2bc0b853e76..7983a92cc8a 100644 --- a/pkg/services/frontend/request_config_middleware_test.go +++ b/pkg/services/frontend/request_config_middleware_test.go @@ -8,6 +8,7 @@ import ( "gopkg.in/ini.v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apiserver/pkg/endpoints/request" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/services/contexthandler/ctxkey" @@ -22,13 +23,16 @@ import ( ) // setupTestContext creates a request with a proper context that includes a logger -func setupTestContext(r *http.Request) *http.Request { +func setupTestContext(r *http.Request, namespace string) *http.Request { logger := log.NewNopLogger() reqCtx := &contextmodel.ReqContext{ Context: &web.Context{Req: r}, Logger: logger, } ctx := ctxkey.Set(r.Context(), reqCtx) + if namespace != "" { + ctx = request.WithNamespace(ctx, namespace) + } return r.WithContext(ctx) } @@ -56,7 +60,7 @@ func TestRequestConfigMiddleware(t *testing.T) { handler := middleware(testHandler) req := httptest.NewRequest("GET", "/", nil) - req = setupTestContext(req) + req = setupTestContext(req, "stacks-123") recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, req) @@ -88,7 +92,7 @@ func TestRequestConfigMiddleware(t *testing.T) { handler := middleware(testHandler) req := httptest.NewRequest("GET", "/", nil) - req = setupTestContext(req) + req = setupTestContext(req, "stacks-123") recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, req) @@ -128,8 +132,7 @@ func TestRequestConfigMiddleware(t *testing.T) { handler := middleware(testHandler) req := httptest.NewRequest("GET", "/", nil) - req.Header.Set("baggage", "namespace=stacks-123") - req = setupTestContext(req) + req = setupTestContext(req, "stacks-123") recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, req) @@ -175,8 +178,7 @@ func TestRequestConfigMiddleware(t *testing.T) { handler := middleware(testHandler) req := httptest.NewRequest("GET", "/", nil) - req.Header.Set("baggage", "namespace=stacks-123") - req = setupTestContext(req) + req = setupTestContext(req, "stacks-123") recorder := httptest.NewRecorder() handler.ServeHTTP(recorder, req) @@ -192,7 +194,7 @@ func TestRequestConfigMiddleware(t *testing.T) { assert.True(t, mockSettingsService.called) }) - t.Run("should not call settings service without namespace header", func(t *testing.T) { + t.Run("should not call settings service when no namespace is present", func(t *testing.T) { mockSettingsService := &mockSettingsService{} license := &licensing.OSSLicensingService{} @@ -211,7 +213,7 @@ func TestRequestConfigMiddleware(t *testing.T) { handler := middleware(testHandler) req := httptest.NewRequest("GET", "/", nil) - req = setupTestContext(req) + req = setupTestContext(req, "") // No baggage header recorder := httptest.NewRecorder() @@ -220,103 +222,6 @@ func TestRequestConfigMiddleware(t *testing.T) { assert.Equal(t, http.StatusOK, recorder.Code) assert.False(t, mockSettingsService.called) }) - - t.Run("should parse namespace from baggage header with multiple values", func(t *testing.T) { - mockSettingsService := &mockSettingsService{ - settings: []*settingservice.Setting{ - {Section: "security", Key: "content_security_policy", Value: "true"}, - }, - } - - license := &licensing.OSSLicensingService{} - cfg := &setting.Cfg{ - Raw: ini.Empty(), - HTTPPort: "1234", - CSPEnabled: false, // Base config has CSP disabled - } - - middleware := RequestConfigMiddleware(cfg, license, mockSettingsService) - - var capturedConfig FSRequestConfig - testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var err error - capturedConfig, err = FSRequestConfigFromContext(r.Context()) - require.NoError(t, err) - w.WriteHeader(http.StatusOK) - }) - - handler := middleware(testHandler) - - req := httptest.NewRequest("GET", "/", nil) - // Baggage header with multiple key-value pairs - req.Header.Set("baggage", "trace-id=abc123,namespace=tenant-456,user-id=xyz") - req = setupTestContext(req) - recorder := httptest.NewRecorder() - - handler.ServeHTTP(recorder, req) - - assert.Equal(t, http.StatusOK, recorder.Code) - assert.True(t, capturedConfig.CSPEnabled, "Should apply tenant overrides when namespace is present") - assert.True(t, mockSettingsService.called, "Should call settings service when namespace is in baggage") - }) - - t.Run("should not call settings service with malformed baggage header", func(t *testing.T) { - mockSettingsService := &mockSettingsService{} - - license := &licensing.OSSLicensingService{} - cfg := &setting.Cfg{ - Raw: ini.Empty(), - HTTPPort: "1234", - } - - middleware := RequestConfigMiddleware(cfg, license, mockSettingsService) - - testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - handler := middleware(testHandler) - - req := httptest.NewRequest("GET", "/", nil) - // Malformed baggage header - req.Header.Set("baggage", "invalid baggage format;;;") - req = setupTestContext(req) - recorder := httptest.NewRecorder() - - handler.ServeHTTP(recorder, req) - - assert.Equal(t, http.StatusOK, recorder.Code) - assert.False(t, mockSettingsService.called, "Should not call settings service with malformed baggage") - }) - - t.Run("should not call settings service when baggage has no namespace", func(t *testing.T) { - mockSettingsService := &mockSettingsService{} - - license := &licensing.OSSLicensingService{} - cfg := &setting.Cfg{ - Raw: ini.Empty(), - HTTPPort: "1234", - } - - middleware := RequestConfigMiddleware(cfg, license, mockSettingsService) - - testHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - }) - - handler := middleware(testHandler) - - req := httptest.NewRequest("GET", "/", nil) - // Baggage header without namespace key - req.Header.Set("baggage", "trace-id=abc123,user-id=xyz") - req = setupTestContext(req) - recorder := httptest.NewRecorder() - - handler.ServeHTTP(recorder, req) - - assert.Equal(t, http.StatusOK, recorder.Code) - assert.False(t, mockSettingsService.called, "Should not call settings service when namespace is not in baggage") - }) } // mockSettingsService is a simple mock for testing diff --git a/pkg/services/frontend/webassets/webassets.go b/pkg/services/frontend/webassets/webassets.go index 295406e84e4..61967c80261 100644 --- a/pkg/services/frontend/webassets/webassets.go +++ b/pkg/services/frontend/webassets/webassets.go @@ -1,6 +1,7 @@ package fswebassets import ( + "context" "path/filepath" "github.com/grafana/grafana/pkg/api/dtos" @@ -34,8 +35,9 @@ func getCDNRoot(cfg *setting.Cfg, license licensing.Licensing) string { } // New codepath for retrieving web assets URLs for the frontend-service -func GetWebAssets(cfg *setting.Cfg, license licensing.Licensing) (dtos.EntryPointAssets, error) { - assetsManifest, err := webassets.ReadWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", "assets-manifest.json")) +func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licensing) (dtos.EntryPointAssets, error) { + assetsFilename := "assets-manifest.json" + assetsManifest, err := webassets.ReadWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", assetsFilename)) if err != nil { return dtos.EntryPointAssets{}, err } diff --git a/pkg/services/frontend/webassets/webassets_test.go b/pkg/services/frontend/webassets/webassets_test.go index bd169835e70..94d35a77b95 100644 --- a/pkg/services/frontend/webassets/webassets_test.go +++ b/pkg/services/frontend/webassets/webassets_test.go @@ -1,6 +1,7 @@ package fswebassets_test import ( + "context" "net/url" "testing" @@ -16,8 +17,9 @@ func TestGetWebAssets_WithoutCDNConfigured(t *testing.T) { } license := licensingtest.NewFakeLicensing() license.On("ContentDeliveryPrefix").Return("grafana") + ctx := context.Background() - assets, err := fswebassets.GetWebAssets(cfg, license) + assets, err := fswebassets.GetWebAssets(ctx, cfg, license) assert.NoError(t, err) assert.NotNil(t, assets) @@ -33,8 +35,9 @@ func TestGetWebAssets_PrefixFromLicense(t *testing.T) { } license := licensingtest.NewFakeLicensing() license.On("ContentDeliveryPrefix").Return("grafana-pro-max") + ctx := context.Background() - assets, err := fswebassets.GetWebAssets(cfg, license) + assets, err := fswebassets.GetWebAssets(ctx, cfg, license) assert.NoError(t, err) assert.NotNil(t, assets) @@ -49,8 +52,9 @@ func TestGetWebAssets_PrefixFromConfig(t *testing.T) { } license := licensingtest.NewFakeLicensing() license.On("ContentDeliveryPrefix").Return("should-not-be-used") + ctx := context.Background() - assets, err := fswebassets.GetWebAssets(cfg, license) + assets, err := fswebassets.GetWebAssets(ctx, cfg, license) assert.NoError(t, err) assert.NotNil(t, assets) @@ -66,8 +70,9 @@ func TestGetWebAssets_PrefixFromConfigTrailingSlash(t *testing.T) { } license := licensingtest.NewFakeLicensing() license.On("ContentDeliveryPrefix").Return("should-not-be-used") + ctx := context.Background() - assets, err := fswebassets.GetWebAssets(cfg, license) + assets, err := fswebassets.GetWebAssets(ctx, cfg, license) assert.NoError(t, err) assert.NotNil(t, assets)