FS: Get CDN prefix from configuration (#110615)

* FS: Get CDN prefix from configuration

* undo logger change

* fix tests

* add unused property

* tests

* fix tests

* remove dead comment
This commit is contained in:
Josh Hunt
2025-09-08 15:57:03 +00:00
committed by GitHub
parent 2f2950eb29
commit 454380431d
10 changed files with 143 additions and 52 deletions
-7
View File
@@ -47,7 +47,6 @@ import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/frontend"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginaccesscontrol"
publicdashboardsapi "github.com/grafana/grafana/pkg/services/publicdashboards/api"
@@ -86,12 +85,6 @@ func (hs *HTTPServer) registerRoutes() {
r.Get("/invite/:code", hs.Index)
if hs.Features.IsEnabledGlobally(featuremgmt.FlagMultiTenantFrontend) {
index, err := frontend.NewIndexProvider(hs.Cfg, hs.License)
if err != nil {
panic(err) // ???
}
r.Get("/femt", index.HandleRequest)
// Temporarily expose the full bootdata via API
r.Get("/bootdata", reqNoAuth, hs.GetBootdata)
}
+2 -2
View File
@@ -60,7 +60,7 @@ func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licen
}
if result == nil {
result, err = readWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", "assets-manifest.json"))
result, err = ReadWebAssetsFromFile(filepath.Join(cfg.StaticRootPath, "build", "assets-manifest.json"))
if err == nil {
cdn, _ = cfg.GetContentDeliveryURL(license.ContentDeliveryPrefix())
if cdn != "" {
@@ -73,7 +73,7 @@ func GetWebAssets(ctx context.Context, cfg *setting.Cfg, license licensing.Licen
return entryPointAssetsCache, err
}
func readWebAssetsFromFile(manifestpath string) (*dtos.EntryPointAssets, error) {
func ReadWebAssetsFromFile(manifestpath string) (*dtos.EntryPointAssets, error) {
//nolint:gosec
f, err := os.Open(manifestpath)
if err != nil {
+1 -1
View File
@@ -10,7 +10,7 @@ import (
)
func TestReadWebassets(t *testing.T) {
assets, err := readWebAssetsFromFile("testdata/sample-assets-manifest.json")
assets, err := ReadWebAssetsFromFile("testdata/build/assets-manifest.json")
require.NoError(t, err)
dto, err := json.MarshalIndent(assets, "", " ")
+7 -1
View File
@@ -17,6 +17,7 @@ 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/licensing"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
@@ -40,7 +41,12 @@ type frontendService struct {
}
func ProvideFrontendService(cfg *setting.Cfg, features featuremgmt.FeatureToggles, promGatherer prometheus.Gatherer, promRegister prometheus.Registerer, license licensing.Licensing) (*frontendService, error) {
index, err := NewIndexProvider(cfg, license)
assetsManifest, err := fswebassets.GetWebAssets(cfg, license)
if err != nil {
return nil, err
}
index, err := NewIndexProvider(cfg, assetsManifest)
if err != nil {
return nil, err
}
@@ -28,6 +28,10 @@ func createTestService(t *testing.T, cfg *setting.Cfg) *frontendService {
var promRegister prometheus.Registerer = prometheus.NewRegistry()
promGatherer := promRegister.(*prometheus.Registry)
if cfg.BuildVersion == "" {
cfg.BuildVersion = "10.3.0"
}
service, err := ProvideFrontendService(cfg, features, promGatherer, promRegister, license)
require.NoError(t, err)
+5 -19
View File
@@ -1,7 +1,6 @@
package frontend
import (
"context"
"embed"
"errors"
"fmt"
@@ -11,9 +10,7 @@ import (
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/webassets"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/setting"
)
@@ -29,15 +26,14 @@ type IndexViewData struct {
CSPEnabled bool
IsDevelopmentEnv bool
Config *setting.Cfg
License licensing.Licensing
Config *setting.Cfg
AppSubUrl string
BuildVersion string
BuildCommit string
AppTitle string
Assets *dtos.EntryPointAssets // Includes CDN info
Assets dtos.EntryPointAssets // Includes CDN info
// Nonce is a cryptographic identifier for use with Content Security Policy.
Nonce string
@@ -52,7 +48,7 @@ var (
htmlTemplates = template.Must(template.New("html").Delims("[[", "]]").ParseFS(templatesFS, `*.html`))
)
func NewIndexProvider(cfg *setting.Cfg, license licensing.Licensing) (*IndexProvider, error) {
func NewIndexProvider(cfg *setting.Cfg, assetsManifest dtos.EntryPointAssets) (*IndexProvider, error) {
t := htmlTemplates.Lookup("index.html")
if t == nil {
return nil, fmt.Errorf("missing index template")
@@ -67,13 +63,14 @@ func NewIndexProvider(cfg *setting.Cfg, license licensing.Licensing) (*IndexProv
BuildVersion: cfg.BuildVersion,
BuildCommit: cfg.BuildCommit,
Config: cfg,
License: license,
CSPEnabled: cfg.CSPEnabled,
CSPContent: cfg.CSPTemplate,
CSPReportOnlyContent: cfg.CSPReportOnlyTemplate,
IsDevelopmentEnv: cfg.Env == setting.Dev,
Assets: assetsManifest,
},
}, nil
}
@@ -106,17 +103,6 @@ func (p *IndexProvider) HandleRequest(writer http.ResponseWriter, request *http.
writer.Header().Set("Content-Security-Policy-Report-Only", policy)
}
// TODO: moved to request handler to prevent stale assets during dev,
// but should we do this differently?
assets, err := webassets.GetWebAssets(context.Background(), data.Config, data.License)
if err != nil {
p.log.Error("error getting assets", "err", err)
writer.WriteHeader(500)
return
}
data.Assets = assets
writer.Header().Set("Content-Type", "text/html; charset=UTF-8")
writer.WriteHeader(200)
if err := p.index.Execute(writer, &data); err != nil {
-22
View File
@@ -98,26 +98,4 @@ func TestFrontendService_WebAssets(t *testing.T) {
assert.Contains(t, body, "src=\"public/build/runtime.js\" type=\"text/javascript\"")
assert.Contains(t, body, "src=\"public/build/app.js\" type=\"text/javascript\"")
})
t.Run("should handle missing assets manifest gracefully", func(t *testing.T) {
cfg := &setting.Cfg{
HTTPPort: "3000",
StaticRootPath: "/dev/null", // No build directory or manifest
Env: setting.Dev, // needs to be dev to bypass the cache
}
service := createTestService(t, cfg)
mux := web.New()
service.addMiddlewares(mux)
service.registerRoutes(mux)
// Test index route which should fail to load web assets
req := httptest.NewRequest("GET", "/", nil)
recorder := httptest.NewRecorder()
mux.ServeHTTP(recorder, req)
// Should return 500 due to missing assets manifest
assert.Equal(t, 500, recorder.Code)
})
}
@@ -0,0 +1,49 @@
package fswebassets
import (
"path/filepath"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/api/webassets"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/setting"
)
var logger = log.New("webassets")
func getCDNRoot(cfg *setting.Cfg, license licensing.Licensing) string {
if cfg.CDNRootURL == nil {
return ""
}
// We prefer to set the prefix from config, but make this backwards compatible
// taking it from the license instead
var prefix string
if cfg.CDNRootURL.Path == "" {
prefix = license.ContentDeliveryPrefix()
}
cdnRoot, err := cfg.GetContentDeliveryURL(prefix)
if err != nil {
logger.Error("error getting cdn url from config", "error", err)
return ""
}
return cdnRoot
}
// 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"))
if err != nil {
return dtos.EntryPointAssets{}, err
}
cdnRoot := getCDNRoot(cfg, license)
if cdnRoot != "" {
assetsManifest.SetContentDeliveryURL(cdnRoot)
}
return *assetsManifest, nil
}
@@ -0,0 +1,75 @@
package fswebassets_test
import (
"net/url"
"testing"
fswebassets "github.com/grafana/grafana/pkg/services/frontend/webassets"
"github.com/grafana/grafana/pkg/services/licensing/licensingtest"
"github.com/grafana/grafana/pkg/setting"
"github.com/stretchr/testify/assert"
)
func TestGetWebAssets_WithoutCDNConfigured(t *testing.T) {
cfg := &setting.Cfg{
StaticRootPath: "../../../api/webassets/testdata",
}
license := licensingtest.NewFakeLicensing()
license.On("ContentDeliveryPrefix").Return("grafana")
assets, err := fswebassets.GetWebAssets(cfg, license)
assert.NoError(t, err)
assert.NotNil(t, assets)
assert.Equal(t, "public/build/runtime.js", assets.JSFiles[0].FilePath)
}
func TestGetWebAssets_PrefixFromLicense(t *testing.T) {
cdnConfigUrl, _ := url.Parse("http://example.com")
cfg := &setting.Cfg{
StaticRootPath: "../../../api/webassets/testdata",
CDNRootURL: cdnConfigUrl,
BuildVersion: "10.3.0",
}
license := licensingtest.NewFakeLicensing()
license.On("ContentDeliveryPrefix").Return("grafana-pro-max")
assets, err := fswebassets.GetWebAssets(cfg, license)
assert.NoError(t, err)
assert.NotNil(t, assets)
assert.Equal(t, "http://example.com/grafana-pro-max/10.3.0/public/build/runtime.js", assets.JSFiles[0].FilePath)
}
func TestGetWebAssets_PrefixFromConfig(t *testing.T) {
cdnConfigUrl, _ := url.Parse("http://example.com/grafana-super-plus")
cfg := &setting.Cfg{
StaticRootPath: "../../../api/webassets/testdata",
CDNRootURL: cdnConfigUrl,
BuildVersion: "10.3.0",
}
license := licensingtest.NewFakeLicensing()
license.On("ContentDeliveryPrefix").Return("should-not-be-used")
assets, err := fswebassets.GetWebAssets(cfg, license)
assert.NoError(t, err)
assert.NotNil(t, assets)
assert.Equal(t, "http://example.com/grafana-super-plus/10.3.0/public/build/runtime.js", assets.JSFiles[0].FilePath)
}
func TestGetWebAssets_PrefixFromConfigTrailingSlash(t *testing.T) {
cdnConfigUrl, _ := url.Parse("http://example.com/grafana-mega/")
cfg := &setting.Cfg{
StaticRootPath: "../../../api/webassets/testdata",
CDNRootURL: cdnConfigUrl,
BuildVersion: "10.3.0",
}
license := licensingtest.NewFakeLicensing()
license.On("ContentDeliveryPrefix").Return("should-not-be-used")
assets, err := fswebassets.GetWebAssets(cfg, license)
assert.NoError(t, err)
assert.NotNil(t, assets)
assert.Equal(t, "http://example.com/grafana-mega/10.3.0/public/build/runtime.js", assets.JSFiles[0].FilePath)
}